├── lib ├── errors │ ├── index.ts │ └── vk.exception.ts ├── services │ ├── index.ts │ ├── base-explorer.service.ts │ ├── metadata-accessor.service.ts │ └── listeners-explorer.service.ts ├── decorators │ ├── listeners │ │ ├── index.ts │ │ ├── use.decorator.ts │ │ ├── hears.decorator.ts │ │ └── on.decorator.ts │ ├── core │ │ ├── index.ts │ │ ├── inject-vk-api.decorator.ts │ │ ├── scene.decorator.ts │ │ └── update.decorator.ts │ ├── scene │ │ ├── index.ts │ │ ├── scene-enter.decorator.ts │ │ ├── scene-leave.decorator.ts │ │ └── add-step.decorator.ts │ ├── index.ts │ └── params │ │ ├── index.ts │ │ ├── next.decorator.ts │ │ ├── message.decorator.ts │ │ └── context.decorator.ts ├── enums │ └── vk-paramtype.enum.ts ├── providers │ ├── index.ts │ ├── hear-manager.provider.ts │ ├── scene-manager.provider.ts │ └── session-manager.provider.ts ├── execution-context │ ├── index.ts │ ├── vk-arguments-host.interface.ts │ ├── vk-arguments-host.ts │ └── vk-execution-context.ts ├── interfaces │ ├── index.ts │ ├── vk-exception-filter.interface.ts │ ├── listener-metadata.interface.ts │ └── vk-options.interface.ts ├── utils │ ├── index.ts │ ├── get-vk-api-token.util.ts │ ├── create-vk-api-factory.util.ts │ ├── create-listener-decorator.util.ts │ └── param-decorator.util.ts ├── index.ts ├── types │ ├── index.d.ts │ └── index.ts ├── factories │ └── vk-params-factory.ts ├── vk.constants.ts ├── vk.module.ts └── vk-core.module.ts ├── sample ├── 01-complete-app │ ├── .env.example │ ├── .prettierrc │ ├── README.md │ ├── tsconfig.build.json │ ├── src │ │ ├── echo │ │ │ ├── echo.service.ts │ │ │ ├── echo.module.ts │ │ │ └── echo.update.ts │ │ ├── interfaces │ │ │ └── context.interface.ts │ │ ├── common │ │ │ ├── index.ts │ │ │ ├── pipes │ │ │ │ └── reverse-text.pipe.ts │ │ │ ├── decorators │ │ │ │ └── update-type.decorator.ts │ │ │ ├── filters │ │ │ │ └── vk-exception.filter.ts │ │ │ └── guards │ │ │ │ └── admin.guard.ts │ │ ├── main.ts │ │ ├── middleware │ │ │ └── features.middleware.ts │ │ └── app.module.ts │ ├── nest-cli.json │ ├── jest.json │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── package.json │ └── .gitignore └── 02-multibots-app │ ├── .prettierrc │ ├── README.md │ ├── .env.example │ ├── src │ ├── common │ │ ├── index.ts │ │ ├── filters │ │ │ └── vk-exception.filter.ts │ │ └── guards │ │ │ └── admin.guard.ts │ ├── interfaces │ │ ├── best-scene-context-state.ts │ │ └── context.interface.ts │ ├── bot-second │ │ ├── bot-second.module.ts │ │ └── bot-second.update.ts │ ├── main.ts │ ├── bot-partial │ │ ├── bot-partial.module.ts │ │ ├── scenes │ │ │ └── best.scene.ts │ │ └── bot-partial.update.ts │ └── app.module.ts │ ├── tsconfig.build.json │ ├── nest-cli.json │ ├── jest.json │ ├── .gitignore │ ├── tsconfig.json │ ├── .eslintrc.js │ └── package.json ├── .prettierrc ├── .gitignore ├── .npmignore ├── tsconfig.json ├── .versionrc.json ├── package.json ├── CHANGELOG.md └── README.md /lib/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vk.exception'; 2 | -------------------------------------------------------------------------------- /lib/errors/vk.exception.ts: -------------------------------------------------------------------------------- 1 | export class VkException extends Error {} 2 | -------------------------------------------------------------------------------- /sample/01-complete-app/.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN= 2 | BOT_GROUP_ID= 3 | ADMIN_IDS= 4 | -------------------------------------------------------------------------------- /sample/01-complete-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /sample/02-multibots-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /sample/01-complete-app/README.md: -------------------------------------------------------------------------------- 1 | ## Running 2 | 3 | - Rename .env.example to .env 4 | - Fill .env 5 | -------------------------------------------------------------------------------- /sample/02-multibots-app/README.md: -------------------------------------------------------------------------------- 1 | ## Running 2 | 3 | - Rename .env.example to .env 4 | - Fill .env 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "printWidth": 120, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /lib/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './listeners-explorer.service'; 2 | export * from './metadata-accessor.service'; 3 | -------------------------------------------------------------------------------- /sample/02-multibots-app/.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN_1= 2 | BOT_GROUP_ID_1= 3 | BOT_TOKEN_2= 4 | BOT_GROUP_ID_2= 5 | ADMIN_IDS= 6 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './filters/vk-exception.filter'; 2 | export * from './guards/admin.guard'; 3 | -------------------------------------------------------------------------------- /lib/decorators/listeners/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hears.decorator'; 2 | export * from './on.decorator'; 3 | export * from './use.decorator'; 4 | -------------------------------------------------------------------------------- /lib/decorators/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './inject-vk-api.decorator'; 2 | export * from './scene.decorator'; 3 | export * from './update.decorator'; 4 | -------------------------------------------------------------------------------- /sample/01-complete-app/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/02-multibots-app/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /lib/decorators/scene/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add-step.decorator'; 2 | export * from './scene-enter.decorator'; 3 | export * from './scene-leave.decorator'; 4 | -------------------------------------------------------------------------------- /lib/enums/vk-paramtype.enum.ts: -------------------------------------------------------------------------------- 1 | export enum VkParamtype { 2 | CONTEXT, 3 | NEXT, 4 | MESSAGE, 5 | // TODO-Possible-Feature: Add more paramtypes 6 | } 7 | -------------------------------------------------------------------------------- /lib/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hear-manager.provider'; 2 | export * from './scene-manager.provider'; 3 | export * from './session-manager.provider'; 4 | -------------------------------------------------------------------------------- /lib/execution-context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vk-arguments-host.interface'; 2 | export * from './vk-arguments-host'; 3 | export * from './vk-execution-context'; 4 | -------------------------------------------------------------------------------- /lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './listener-metadata.interface'; 2 | export * from './vk-exception-filter.interface'; 3 | export * from './vk-options.interface'; 4 | -------------------------------------------------------------------------------- /lib/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | export * from './listeners'; 3 | // export * from './scene'; 4 | export * from './params'; 5 | export * from './scene'; 6 | -------------------------------------------------------------------------------- /lib/decorators/listeners/use.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createListenerDecorator } from '../../utils'; 2 | 3 | export const Use = createListenerDecorator('vk_updates', 'use'); 4 | -------------------------------------------------------------------------------- /lib/decorators/params/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context.decorator'; 2 | export * from './next.decorator'; 3 | export * from './message.decorator'; 4 | // export * from './sender.decorator'; 5 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/interfaces/best-scene-context-state.ts: -------------------------------------------------------------------------------- 1 | export interface BestSceneContextState { 2 | [index: string]: string; 3 | firstName: string; 4 | age: string; 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # IDE 5 | /.idea 6 | /.awcache 7 | /.vscode 8 | 9 | # misc 10 | npm-debug.log 11 | .DS_Store 12 | 13 | # source 14 | dist 15 | 16 | .env 17 | -------------------------------------------------------------------------------- /lib/interfaces/vk-exception-filter.interface.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost } from '@nestjs/common'; 2 | 3 | export interface VkExceptionFilter { 4 | catch(exception: T, host: ArgumentsHost): any; 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-vk-api-factory.util'; 2 | export * from './create-listener-decorator.util'; 3 | export * from './get-vk-api-token.util'; 4 | export * from './param-decorator.util'; 5 | -------------------------------------------------------------------------------- /lib/decorators/core/inject-vk-api.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { getVkApiToken } from '../../utils'; 3 | 4 | export const InjectVkApi = (vkName?: string) => Inject(getVkApiToken(vkName)); 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .github 4 | lib 5 | sample 6 | website 7 | .commitlintrc.json 8 | .eslintrc.js 9 | .prettierrc 10 | package-lock.json 11 | renovate.json 12 | tsconfig.json 13 | tsconfig.typedoc.json 14 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/echo/echo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class EchoService { 5 | echo(text: string): string { 6 | return `Echo: ${text}`; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/interfaces/context.interface.ts: -------------------------------------------------------------------------------- 1 | import { Context as VkContext } from 'vk-io'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 4 | export interface Context extends VkContext {} 5 | -------------------------------------------------------------------------------- /lib/execution-context/vk-arguments-host.interface.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost } from '@nestjs/common'; 2 | 3 | export interface IVkArgumentsHost extends ArgumentsHost { 4 | getContext(): T; 5 | getNext(): T; 6 | } 7 | -------------------------------------------------------------------------------- /lib/interfaces/listener-metadata.interface.ts: -------------------------------------------------------------------------------- 1 | import { HandlerType } from '../types'; 2 | 3 | export interface ListenerMetadata { 4 | handlerType: HandlerType; 5 | method?: string; 6 | event: any; 7 | args: unknown[]; 8 | } 9 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/interfaces/context.interface.ts: -------------------------------------------------------------------------------- 1 | import { Context as VkContext } from 'vk-io'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 4 | export interface Context extends VkContext {} 5 | -------------------------------------------------------------------------------- /sample/01-complete-app/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/02-multibots-app/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/decorators/scene/scene-enter.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { VK_SCENE_ACTION_METADATA } from '../../vk.constants'; 3 | 4 | export const SceneEnter = () => SetMetadata(VK_SCENE_ACTION_METADATA, 'enter'); 5 | -------------------------------------------------------------------------------- /lib/decorators/scene/scene-leave.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { VK_SCENE_ACTION_METADATA } from '../../vk.constants'; 3 | 4 | export const SceneLeave = () => SetMetadata(VK_SCENE_ACTION_METADATA, 'leave'); 5 | -------------------------------------------------------------------------------- /lib/utils/get-vk-api-token.util.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_VK_API_NAME } from '../vk.constants'; 2 | 3 | export function getVkApiToken(name?: string): string { 4 | return name && name !== DEFAULT_VK_API_NAME ? `${name}VkApi` : DEFAULT_VK_API_NAME; 5 | } 6 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators/update-type.decorator'; 2 | 3 | export * from './filters/vk-exception.filter'; 4 | 5 | export * from './guards/admin.guard'; 6 | 7 | export * from './pipes/reverse-text.pipe'; 8 | -------------------------------------------------------------------------------- /lib/decorators/scene/add-step.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { VK_SCENE_STEP_METADATA } from '../../vk.constants'; 3 | 4 | export const AddStep = (step?: number) => SetMetadata(VK_SCENE_STEP_METADATA, step); 5 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/bot-second/bot-second.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { BotSecondlUpdate } from './bot-second.update'; 3 | 4 | @Module({ 5 | providers: [BotSecondlUpdate], 6 | }) 7 | export class BotSecondlModule {} 8 | -------------------------------------------------------------------------------- /lib/decorators/core/scene.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { VK_SCENE_METADATA } from '../../vk.constants'; 3 | 4 | /** 5 | * TODO 6 | */ 7 | export const Scene = (id: string): ClassDecorator => SetMetadata(VK_SCENE_METADATA, id); 8 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | await NestFactory.createApplicationContext(AppModule); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /lib/decorators/params/next.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createVkParamDecorator } from '../../utils/param-decorator.util'; 2 | import { VkParamtype } from '../../enums/vk-paramtype.enum'; 3 | 4 | export const Next: () => ParameterDecorator = createVkParamDecorator(VkParamtype.NEXT); 5 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | await NestFactory.createApplicationContext(AppModule); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/echo/echo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EchoUpdate } from './echo.update'; 3 | import { EchoService } from './echo.service'; 4 | 5 | @Module({ 6 | providers: [EchoUpdate, EchoService], 7 | }) 8 | export class EchoModule {} 9 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/common/pipes/reverse-text.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, PipeTransform } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class ReverseTextPipe implements PipeTransform { 5 | transform(value: string): string { 6 | return value.split('').reverse().join(''); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators'; 2 | export * from './interfaces'; 3 | export * from './utils'; 4 | export * from './types'; 5 | export * from './services'; 6 | export * from './errors'; 7 | export * from './execution-context'; 8 | export * from './vk.constants'; 9 | export * from './vk.module'; 10 | -------------------------------------------------------------------------------- /lib/decorators/listeners/hears.decorator.ts: -------------------------------------------------------------------------------- 1 | import { HearConditions } from '@vk-io/hear'; 2 | import { createListenerDecorator } from '../../utils'; 3 | 4 | export const Hears = createListenerDecorator>('hears'); 5 | export const HearFallback = createListenerDecorator('hears', 'onFallback'); 6 | -------------------------------------------------------------------------------- /lib/decorators/listeners/on.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Context, ContextTypes, ContextSubTypes } from 'vk-io'; 2 | import { Middleware } from 'middleware-io'; 3 | import { createListenerDecorator } from '../../utils'; 4 | 5 | export const On = createListenerDecorator>('vk_updates', 'on'); 6 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/bot-partial/bot-partial.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { BotPartialUpdate } from './bot-partial.update'; 4 | import { BestScene } from './scenes/best.scene'; 5 | 6 | @Module({ 7 | providers: [BotPartialUpdate, BestScene], 8 | }) 9 | export class BotPartialModule {} 10 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/common/decorators/update-type.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { VkExecutionContext } from 'nestjs-vk'; 3 | 4 | export const UpdateType = createParamDecorator( 5 | (_, ctx: ExecutionContext) => 6 | VkExecutionContext.create(ctx).getContext().updateType, 7 | ); 8 | -------------------------------------------------------------------------------- /lib/decorators/core/update.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { VK_UPDATE_METADATA } from '../../vk.constants'; 3 | 4 | /** 5 | * `@Update` decorator, it's like NestJS `@Controller` decorator, 6 | * but for VK-IO Bot API updates. 7 | */ 8 | export const Update = (): ClassDecorator => SetMetadata(VK_UPDATE_METADATA, true); 9 | -------------------------------------------------------------------------------- /sample/01-complete-app/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": [ 3 | "ts", 4 | "tsx", 5 | "js", 6 | "json" 7 | ], 8 | "transform": { 9 | "^.+\\.tsx?$": "ts-jest" 10 | }, 11 | "testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", 12 | "collectCoverageFrom" : ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 13 | "coverageReporters": ["json", "lcov"] 14 | } 15 | -------------------------------------------------------------------------------- /sample/02-multibots-app/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": [ 3 | "ts", 4 | "tsx", 5 | "js", 6 | "json" 7 | ], 8 | "transform": { 9 | "^.+\\.tsx?$": "ts-jest" 10 | }, 11 | "testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", 12 | "collectCoverageFrom" : ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 13 | "coverageReporters": ["json", "lcov"] 14 | } 15 | -------------------------------------------------------------------------------- /lib/utils/create-vk-api-factory.util.ts: -------------------------------------------------------------------------------- 1 | import { VK } from 'vk-io'; 2 | import { VkModuleOptions } from '../interfaces'; 3 | 4 | export async function createVkApiFactory(options: VkModuleOptions): Promise { 5 | const vk = new VK({ token: options.token, ...options.options }); 6 | 7 | if (options.launchOptions !== false) { 8 | await vk.updates.start(options.launchOptions); 9 | } 10 | 11 | return vk; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "removeComments": false, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es2017", 11 | "sourceMap": false, 12 | "rootDir": "./lib", 13 | "outDir": "./dist", 14 | "skipLibCheck": true 15 | }, 16 | "include": ["lib/**/*"], 17 | "exclude": ["node_modules", "**/*.spec.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /lib/providers/hear-manager.provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@nestjs/common'; 2 | import { HearManager } from '@vk-io/hear'; 3 | import { VkManagersOptions } from '../interfaces'; 4 | import { VK_HEAR_MANAGER, VK_MANAGERS_OPTIONS } from '../vk.constants'; 5 | 6 | export const hearManagerProvider: Provider = { 7 | provide: VK_HEAR_MANAGER, 8 | useFactory: (options: VkManagersOptions) => 9 | options.useHearManager instanceof HearManager ? options.useHearManager : new HearManager(), 10 | inject: [VK_MANAGERS_OPTIONS], 11 | }; 12 | -------------------------------------------------------------------------------- /lib/providers/scene-manager.provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@nestjs/common'; 2 | import { SceneManager } from '@vk-io/scenes'; 3 | import { VkManagersOptions } from '../interfaces'; 4 | import { VK_MANAGERS_OPTIONS, VK_SCENE_MANAGER } from '../vk.constants'; 5 | 6 | export const sceneManagerProvider: Provider = { 7 | provide: VK_SCENE_MANAGER, 8 | useFactory: (options: VkManagersOptions) => 9 | options.useSceneManager instanceof SceneManager ? options.useSceneManager : new SceneManager(), 10 | inject: [VK_MANAGERS_OPTIONS], 11 | }; 12 | -------------------------------------------------------------------------------- /lib/providers/session-manager.provider.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@nestjs/common'; 2 | import { SessionManager } from '@vk-io/session'; 3 | import { VkManagersOptions } from '../interfaces'; 4 | import { VK_MANAGERS_OPTIONS, VK_SESSION_MANAGER } from '../vk.constants'; 5 | 6 | export const sessionManagerProvider: Provider = { 7 | provide: VK_SESSION_MANAGER, 8 | useFactory: (options: VkManagersOptions) => 9 | options.useSessionManager instanceof SessionManager ? options.useSessionManager : new SessionManager(), 10 | inject: [VK_MANAGERS_OPTIONS], 11 | }; 12 | -------------------------------------------------------------------------------- /lib/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Composer, Middleware } from 'middleware-io'; 2 | export declare type Filter = T extends [] ? [] : T extends [infer Head, ...infer Tail] ? Head extends F ? Filter : [Head, ...Filter] : []; 3 | export declare type OnlyFunctionPropertyNames = { 4 | [K in keyof T]: T[K] extends (...args: any) => any ? K : never; 5 | }[keyof T]; 6 | export declare type ComposerMethodArgs, U extends OnlyFunctionPropertyNames = OnlyFunctionPropertyNames> = Filter, Middleware>; 7 | -------------------------------------------------------------------------------- /sample/02-multibots-app/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /sample/01-complete-app/src/middleware/features.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { MiddlewareReturn, NextMiddleware } from 'middleware-io'; 3 | import { Context } from 'vk-io'; 4 | 5 | @Injectable() 6 | export class FeaturesMiddleware { 7 | get middleware() { 8 | return async (ctx: Context, next: NextMiddleware): Promise => { 9 | if (ctx.is(['message'])) { 10 | if (ctx.isOutbox) { 11 | return; 12 | } 13 | } 14 | 15 | try { 16 | await next(); 17 | } catch (error) { 18 | console.error('Error:', error); 19 | } 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/01-complete-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/02-multibots-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/types/index.ts: -------------------------------------------------------------------------------- 1 | // import { Composer, Middleware } from 'middleware-io'; 2 | 3 | export type HandlerType = 'vk_updates' | 'hears'; 4 | 5 | // export type Filter = T extends [] 6 | // ? [] 7 | // : T extends [infer Head, ...infer Tail] 8 | // ? Head extends F 9 | // ? Filter 10 | // : [Head, ...Filter] 11 | // : []; 12 | 13 | // export type OnlyFunctionPropertyNames = { 14 | // [K in keyof T]: T[K] extends (...args: any) => any ? K : never; 15 | // }[keyof T]; 16 | 17 | // export type ComposerMethodArgs< 18 | // T extends Composer, 19 | // U extends OnlyFunctionPropertyNames = OnlyFunctionPropertyNames, 20 | // > = Filter, Middleware>; 21 | -------------------------------------------------------------------------------- /lib/execution-context/vk-arguments-host.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost } from '@nestjs/common'; 2 | import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; 3 | import { IVkArgumentsHost } from './vk-arguments-host.interface'; 4 | 5 | export class VkArgumentsHost extends ExecutionContextHost implements IVkArgumentsHost { 6 | static create(context: ArgumentsHost): IVkArgumentsHost { 7 | const type = context.getType(); 8 | const vkContext = new VkArgumentsHost(context.getArgs()); 9 | vkContext.setType(type); 10 | return vkContext; 11 | } 12 | 13 | getContext(): T { 14 | return this.getArgByIndex(0); 15 | } 16 | 17 | getNext(): T { 18 | return this.getArgByIndex(1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/decorators/params/message.decorator.ts: -------------------------------------------------------------------------------- 1 | import { PipeTransform, Type } from '@nestjs/common'; 2 | import { VkParamtype } from '../../enums/vk-paramtype.enum'; 3 | import { createVkPipesParamDecorator } from '../../utils'; 4 | 5 | export function Message(): ParameterDecorator; 6 | export function Message(...pipes: (Type | PipeTransform)[]): ParameterDecorator; 7 | export function Message(property: string, ...pipes: (Type | PipeTransform)[]): ParameterDecorator; 8 | export function Message( 9 | property?: string | (Type | PipeTransform), 10 | ...pipes: (Type | PipeTransform)[] 11 | ) { 12 | return createVkPipesParamDecorator(VkParamtype.MESSAGE)(property, ...pipes); 13 | } 14 | 15 | export const Msg = Message; 16 | -------------------------------------------------------------------------------- /sample/01-complete-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /sample/02-multibots-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/factories/vk-params-factory.ts: -------------------------------------------------------------------------------- 1 | import { ParamData } from '@nestjs/common'; 2 | import { ParamsFactory } from '@nestjs/core/helpers/external-context-creator'; 3 | import { Context } from 'vk-io'; 4 | import { VkParamtype } from '../enums/vk-paramtype.enum'; 5 | 6 | export class VkParamsFactory implements ParamsFactory { 7 | exchangeKeyForValue(type: VkParamtype, data: ParamData, args: unknown[]): unknown { 8 | const [ctx, next] = args as [Context, Function]; 9 | 10 | switch (type) { 11 | case VkParamtype.NEXT: 12 | return next; 13 | case VkParamtype.CONTEXT: 14 | return data && ctx ? ctx[data as string] : ctx; 15 | case VkParamtype.MESSAGE: 16 | return data && ctx.message ? ctx.message?.[data as string] : ctx.message; 17 | default: 18 | return null; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/vk.constants.ts: -------------------------------------------------------------------------------- 1 | import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'; 2 | 3 | export const VK_MODULE_OPTIONS = 'VK_MODULE_OPTIONS'; 4 | export const VK_MANAGERS_OPTIONS = 'VK_MANAGERS_OPTIONS'; 5 | export const VK_API_NAME = 'VK_API_NAME'; 6 | export const DEFAULT_VK_API_NAME = 'DEFAULT_VK_API_NAME'; 7 | 8 | export const VK_UPDATE_METADATA = 'VK_UPDATE_METADATA'; 9 | export const VK_SCENE_METADATA = 'VK_SCENE_METADATA'; 10 | export const VK_LISTENERS_METADATA = 'VK_LISTENERS_METADATA'; 11 | 12 | export const VK_SCENE_STEP_METADATA = 'VK_SCENE_STEP_METADATA'; 13 | export const VK_SCENE_ACTION_METADATA = 'VK_SCENE_ACTION_METADATA'; 14 | 15 | export const PARAM_ARGS_METADATA = ROUTE_ARGS_METADATA; 16 | 17 | export const VK_SESSION_MANAGER = 'VkSessionManager'; 18 | export const VK_SCENE_MANAGER = 'VkSceneManager'; 19 | export const VK_HEAR_MANAGER = 'VkHearManager'; 20 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/common/filters/vk-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; 2 | import { VkArgumentsHost } from 'nestjs-vk'; 3 | import { Context } from '../../interfaces/context.interface'; 4 | import { ADMIN_IDS, NOT_ADMIN_MESSAGE } from '../guards/admin.guard'; 5 | 6 | @Catch() 7 | export class VkExceptionFilter implements ExceptionFilter { 8 | async catch(exception: Error, host: ArgumentsHost): Promise { 9 | const vkContext = VkArgumentsHost.create(host); 10 | const ctx = vkContext.getContext(); 11 | 12 | if (exception.message === NOT_ADMIN_MESSAGE) { 13 | await ctx.reply('You are not admin 😡'); 14 | } else if (ADMIN_IDS.includes(ctx.senderId)) { 15 | await ctx.reply(`Error: ${exception.message}`); 16 | } else { 17 | console.error(exception); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/common/filters/vk-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; 2 | import { VkArgumentsHost } from 'nestjs-vk'; 3 | import { Context } from '../../interfaces/context.interface'; 4 | import { ADMIN_IDS, NOT_ADMIN_MESSAGE } from '../guards/admin.guard'; 5 | 6 | @Catch() 7 | export class VkExceptionFilter implements ExceptionFilter { 8 | async catch(exception: Error, host: ArgumentsHost): Promise { 9 | const vkContext = VkArgumentsHost.create(host); 10 | const ctx = vkContext.getContext(); 11 | 12 | if (exception.message === NOT_ADMIN_MESSAGE) { 13 | await ctx.reply('You are not admin 😡'); 14 | } else if (ADMIN_IDS.includes(ctx.senderId)) { 15 | await ctx.reply(`Error: ${exception.message}`); 16 | } else { 17 | console.error(exception); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/common/guards/admin.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { VkExecutionContext, VkException } from 'nestjs-vk'; 3 | import { Context } from '../../interfaces/context.interface'; 4 | 5 | export const NOT_ADMIN_MESSAGE = 'NOT_ADMIN_MESSAGE'; 6 | export const ADMIN_IDS: number[] = process.env.ADMIN_IDS?.split(',') 7 | .map((s) => Number(s.trim())) 8 | .filter((n) => n > 0); 9 | 10 | @Injectable() 11 | export class AdminGuard implements CanActivate { 12 | canActivate(context: ExecutionContext): boolean { 13 | const ctx = VkExecutionContext.create(context); 14 | const { senderId } = ctx.getContext(); 15 | 16 | const isAdmin = ADMIN_IDS.includes(senderId); 17 | if (!isAdmin) { 18 | throw new VkException(NOT_ADMIN_MESSAGE); 19 | } 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/common/guards/admin.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { VkExecutionContext, VkException } from 'nestjs-vk'; 3 | import { Context } from '../../interfaces/context.interface'; 4 | 5 | export const NOT_ADMIN_MESSAGE = 'NOT_ADMIN_MESSAGE'; 6 | export const ADMIN_IDS: number[] = process.env.ADMIN_IDS?.split(',') 7 | .map((s) => Number(s.trim())) 8 | .filter((n) => n > 0); 9 | 10 | @Injectable() 11 | export class AdminGuard implements CanActivate { 12 | canActivate(context: ExecutionContext): boolean { 13 | const ctx = VkExecutionContext.create(context); 14 | const { senderId } = ctx.getContext(); 15 | 16 | const isAdmin = ADMIN_IDS.includes(senderId); 17 | if (!isAdmin) { 18 | throw new VkException(NOT_ADMIN_MESSAGE); 19 | } 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/decorators/params/context.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createVkPipesParamDecorator } from '../../utils/param-decorator.util'; 2 | import { VkParamtype } from '../../enums/vk-paramtype.enum'; 3 | import { PipeTransform, Type } from '@nestjs/common'; 4 | 5 | // export const Context: () => ParameterDecorator = createVkParamDecorator(VkParamtype.CONTEXT); 6 | 7 | export function Context(): ParameterDecorator; 8 | export function Context(...pipes: (Type | PipeTransform)[]): ParameterDecorator; 9 | export function Context(property: string, ...pipes: (Type | PipeTransform)[]): ParameterDecorator; 10 | export function Context( 11 | property?: string | (Type | PipeTransform), 12 | ...pipes: (Type | PipeTransform)[] 13 | ) { 14 | return createVkPipesParamDecorator(VkParamtype.CONTEXT)(property, ...pipes); 15 | } 16 | 17 | export const Ctx = Context; 18 | -------------------------------------------------------------------------------- /lib/utils/create-listener-decorator.util.ts: -------------------------------------------------------------------------------- 1 | import { VK_LISTENERS_METADATA } from '../vk.constants'; 2 | import { ListenerMetadata } from '../interfaces'; 3 | import { HandlerType } from '../types'; 4 | 5 | export function createListenerDecorator(handlerType: HandlerType, method?: string) { 6 | return (event?: E, ...args: A[]): MethodDecorator => { 7 | return (_target: any, _key?: string | symbol, descriptor?: TypedPropertyDescriptor) => { 8 | const metadata: ListenerMetadata[] = [ 9 | { 10 | handlerType, 11 | method, 12 | event, 13 | args, 14 | }, 15 | ]; 16 | 17 | const previousValue = Reflect.getMetadata(VK_LISTENERS_METADATA, descriptor.value) || []; 18 | const value = [...previousValue, ...metadata]; 19 | Reflect.defineMetadata(VK_LISTENERS_METADATA, value, descriptor.value); 20 | return descriptor; 21 | }; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /lib/execution-context/vk-execution-context.ts: -------------------------------------------------------------------------------- 1 | import { ContextType, ExecutionContext } from '@nestjs/common'; 2 | import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; 3 | import { IVkArgumentsHost } from './vk-arguments-host.interface'; 4 | 5 | export type VkontextType = 'vk-io' | ContextType; 6 | 7 | export class VkExecutionContext extends ExecutionContextHost implements IVkArgumentsHost { 8 | static create(context: ExecutionContext): VkExecutionContext { 9 | const type = context.getType(); 10 | const vkContext = new VkExecutionContext(context.getArgs(), context.getClass(), context.getHandler()); 11 | vkContext.setType(type); 12 | return vkContext; 13 | } 14 | 15 | getType(): TContext { 16 | return super.getType(); 17 | } 18 | 19 | getContext(): T { 20 | return this.getArgByIndex(0); 21 | } 22 | 23 | getNext(): T { 24 | return this.getArgByIndex(1); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { VkModule } from 'nestjs-vk'; 3 | 4 | import { BotPartialModule } from './bot-partial/bot-partial.module'; 5 | import { BotSecondlModule } from './bot-second/bot-second.module'; 6 | 7 | @Global() 8 | @Module({ 9 | imports: [ 10 | VkModule.forRootAsync({ 11 | vkName: 'firstBot', 12 | useFactory: async () => ({ 13 | token: process.env.BOT_TOKEN_1, 14 | options: { 15 | pollingGroupId: +process.env.BOT_GROUP_ID_1, 16 | apiMode: 'sequential', 17 | }, 18 | notReplyMessage: true, 19 | include: [BotPartialModule], 20 | }), 21 | }), 22 | VkModule.forRootAsync({ 23 | vkName: 'secondBot', 24 | useFactory: async () => ({ 25 | token: process.env.BOT_TOKEN_2, 26 | options: { 27 | pollingGroupId: +process.env.BOT_GROUP_ID_2, 28 | apiMode: 'sequential', 29 | }, 30 | notReplyMessage: true, 31 | include: [BotPartialModule, BotSecondlModule], 32 | }), 33 | }), 34 | BotPartialModule, 35 | BotSecondlModule, 36 | ], 37 | providers: [], 38 | exports: [], 39 | }) 40 | export class AppModule {} 41 | -------------------------------------------------------------------------------- /lib/services/base-explorer.service.ts: -------------------------------------------------------------------------------- 1 | import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; 2 | import { Module } from '@nestjs/core/injector/module'; 3 | import { flattenDeep, identity, isEmpty } from 'lodash'; 4 | 5 | export class BaseExplorerService { 6 | getModules(modulesContainer: Map, include: Function[]): Module[] { 7 | if (!include || isEmpty(include)) { 8 | return [...modulesContainer.values()]; 9 | } 10 | const whitelisted = this.includeWhitelisted(modulesContainer, include); 11 | return whitelisted; 12 | } 13 | 14 | includeWhitelisted(modulesContainer: Map, include: Function[]): Module[] { 15 | const modules = [...modulesContainer.values()]; 16 | return modules.filter(({ metatype }) => include.includes(metatype)); 17 | } 18 | 19 | flatMap(modules: Module[], callback: (instance: InstanceWrapper, moduleRef: Module) => T | T[]): T[] { 20 | const invokeMap = () => { 21 | return modules.map((moduleRef) => { 22 | const providers = [...moduleRef.providers.values()]; 23 | return providers.map((wrapper) => callback(wrapper, moduleRef)); 24 | }); 25 | }; 26 | return flattenDeep(invokeMap()).filter(identity); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/vk.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, DynamicModule } from '@nestjs/common'; 2 | import { VkCoreModule } from './vk-core.module'; 3 | import { VkModuleOptions, VkModuleAsyncOptions, VkManagersOptions } from './interfaces'; 4 | import { VK_MANAGERS_OPTIONS } from './vk.constants'; 5 | import { sessionManagerProvider, sceneManagerProvider, hearManagerProvider } from './providers'; 6 | 7 | @Module({}) 8 | export class VkModule { 9 | public static forRoot(options: VkModuleOptions): DynamicModule { 10 | return { 11 | module: VkModule, 12 | imports: [VkCoreModule.forRoot(options)], 13 | }; 14 | } 15 | 16 | public static forManagers(options: VkManagersOptions): DynamicModule { 17 | const providers = [ 18 | { 19 | provide: VK_MANAGERS_OPTIONS, 20 | useValue: options, 21 | }, 22 | sessionManagerProvider, 23 | sceneManagerProvider, 24 | hearManagerProvider, 25 | ]; 26 | 27 | return { 28 | module: VkModule, 29 | providers, 30 | exports: [...providers], 31 | global: true, 32 | }; 33 | } 34 | 35 | public static forRootAsync(options: VkModuleAsyncOptions): DynamicModule { 36 | return { 37 | module: VkModule, 38 | imports: [VkCoreModule.forRootAsync(options)], 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/services/metadata-accessor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { 4 | VK_SCENE_METADATA, 5 | VK_LISTENERS_METADATA, 6 | VK_UPDATE_METADATA, 7 | VK_SCENE_STEP_METADATA, 8 | VK_SCENE_ACTION_METADATA, 9 | } from '../vk.constants'; 10 | import { ListenerMetadata } from '../interfaces'; 11 | 12 | @Injectable() 13 | export class MetadataAccessorService { 14 | constructor(private readonly reflector: Reflector) {} 15 | 16 | isUpdate(target: Function): boolean { 17 | if (!target) return false; 18 | return !!this.reflector.get(VK_UPDATE_METADATA, target); 19 | } 20 | 21 | isScene(target: Function): boolean { 22 | if (!target) return false; 23 | return !!this.reflector.get(VK_SCENE_METADATA, target); 24 | } 25 | 26 | getListenerMetadata(target: Function): ListenerMetadata[] | undefined { 27 | return this.reflector.get(VK_LISTENERS_METADATA, target); 28 | } 29 | 30 | getSceneMetadata(target: Function): string | undefined { 31 | return this.reflector.get(VK_SCENE_METADATA, target); 32 | } 33 | 34 | getSceneStepMetadata(target: Function): number | undefined { 35 | return this.reflector.get(VK_SCENE_STEP_METADATA, target); 36 | } 37 | 38 | getSceneActionMetadata(target: Function): 'enter' | 'leave' { 39 | return this.reflector.get(VK_SCENE_ACTION_METADATA, target); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module, OnModuleInit } from '@nestjs/common'; 2 | import { InjectVkApi, VkModule } from 'nestjs-vk'; 3 | import { VK } from 'vk-io'; 4 | 5 | import { FeaturesMiddleware } from './middleware/features.middleware'; 6 | 7 | import { EchoModule } from './echo/echo.module'; 8 | 9 | const middlewares = [FeaturesMiddleware]; 10 | 11 | @Global() 12 | @Module({ 13 | imports: [ 14 | VkModule.forRootAsync({ 15 | useFactory: async (featuresMiddleware: FeaturesMiddleware) => ({ 16 | token: process.env.BOT_TOKEN, 17 | options: { 18 | pollingGroupId: +process.env.BOT_GROUP_ID, 19 | apiMode: 'sequential', 20 | }, 21 | // useSessionManager: false, 22 | // useHearManager: false, 23 | launchOptions: false, 24 | notReplyMessage: true, 25 | middlewaresBefore: [featuresMiddleware.middleware], 26 | include: [EchoModule], 27 | }), 28 | inject: [FeaturesMiddleware], 29 | }), 30 | EchoModule, 31 | ], 32 | providers: [...middlewares], 33 | exports: [...middlewares], 34 | }) 35 | export class AppModule implements OnModuleInit { 36 | constructor( 37 | @InjectVkApi() 38 | private readonly vk: VK, 39 | ) {} 40 | 41 | async onModuleInit() { 42 | try { 43 | await this.vk.updates.start(); 44 | } catch (err) { 45 | console.error(err); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/interfaces/vk-options.interface.ts: -------------------------------------------------------------------------------- 1 | import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; 2 | import { Middleware } from 'middleware-io'; 3 | import { IWebhookTransportStartOptions, MessageContext } from 'vk-io'; 4 | import { VKOptions } from 'vk-io/lib/types'; 5 | import { SessionManager } from '@vk-io/session'; 6 | import { SceneManager } from '@vk-io/scenes'; 7 | import { HearManager } from '@vk-io/hear'; 8 | 9 | export interface VkManagersOptions { 10 | useSessionManager?: boolean | SessionManager; 11 | useSceneManager?: boolean | SceneManager; 12 | useHearManager?: boolean | HearManager; 13 | } 14 | 15 | export interface VkModuleOptions { 16 | token: string; 17 | vkName?: string; 18 | options?: Partial; 19 | launchOptions?: { webhook?: IWebhookTransportStartOptions } | false; 20 | include?: Function[]; 21 | middlewaresBefore?: ReadonlyArray>; 22 | middlewaresAfter?: ReadonlyArray>; 23 | notReplyMessage?: boolean; 24 | } 25 | 26 | export interface VkOptionsFactory { 27 | createVkOptions(): Promise | VkModuleOptions; 28 | } 29 | 30 | export interface VkModuleAsyncOptions extends Pick { 31 | vkName?: string; 32 | useExisting?: Type; 33 | useClass?: Type; 34 | useFactory?: (...args: any[]) => Promise | VkModuleOptions; 35 | inject?: any[]; 36 | } 37 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/bot-second/bot-second.update.ts: -------------------------------------------------------------------------------- 1 | import { Inject, UseFilters } from '@nestjs/common'; 2 | import { 3 | Update, 4 | Ctx, 5 | Hears, 6 | VK_HEAR_MANAGER, 7 | VK_SESSION_MANAGER, 8 | } from 'nestjs-vk'; 9 | import { MessageContext } from 'vk-io'; 10 | import { HearManager } from '@vk-io/hear'; 11 | import { SessionManager } from '@vk-io/session'; 12 | 13 | import { VkExceptionFilter } from '../common'; 14 | 15 | @Update() 16 | @UseFilters(VkExceptionFilter) 17 | export class BotSecondlUpdate { 18 | constructor( 19 | @Inject(VK_HEAR_MANAGER) 20 | private readonly hearManagerProvider: HearManager, 21 | @Inject(VK_SESSION_MANAGER) 22 | private readonly sessionManagerProvider: SessionManager, 23 | ) { 24 | this.hearManagerProvider.hear('wow', (ctx) => { 25 | ctx.send('Wohoh'); 26 | }); 27 | 28 | console.log('[BotSecondlUpdate] ctr', this.hearManagerProvider.length); 29 | } 30 | 31 | @Hears('/second') 32 | onSecondCommand(@Ctx() ctx: MessageContext) { 33 | ctx.send({ sticker_id: 2 }); 34 | } 35 | 36 | @Hears('/info2') 37 | onInfoCommand(@Ctx() ctx: MessageContext) { 38 | const message = ` 39 | Info: 40 | • hearManagerProvider.length: ${this.hearManagerProvider.length} 41 | • sessionManagerProvider.length: ${JSON.stringify( 42 | this.sessionManagerProvider, 43 | ).slice(0, 150)} 44 | `; 45 | return message; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/utils/param-decorator.util.ts: -------------------------------------------------------------------------------- 1 | import { assignMetadata, PipeTransform, Type } from '@nestjs/common'; 2 | import { isNil, isString } from '@nestjs/common/utils/shared.utils'; 3 | import { VkParamtype } from '../enums/vk-paramtype.enum'; 4 | import { PARAM_ARGS_METADATA } from '../vk.constants'; 5 | 6 | export type ParamData = object | string | number; 7 | 8 | export const createVkParamDecorator = (paramtype: VkParamtype) => { 9 | return (data?: ParamData): ParameterDecorator => 10 | (target, key, index) => { 11 | const args = Reflect.getMetadata(PARAM_ARGS_METADATA, target.constructor, key) || {}; 12 | Reflect.defineMetadata( 13 | PARAM_ARGS_METADATA, 14 | assignMetadata(args, paramtype, index, data), 15 | target.constructor, 16 | key, 17 | ); 18 | }; 19 | }; 20 | 21 | export const createVkPipesParamDecorator = 22 | (paramtype: VkParamtype) => 23 | (data?: any, ...pipes: (Type | PipeTransform)[]): ParameterDecorator => 24 | (target, key, index) => { 25 | addPipesMetadata(paramtype, data, pipes, target, key, index); 26 | }; 27 | 28 | export const addPipesMetadata = ( 29 | paramtype: VkParamtype, 30 | data: any, 31 | pipes: (Type | PipeTransform)[], 32 | target: Record, 33 | key: string | symbol, 34 | index: number, 35 | ) => { 36 | const args = Reflect.getMetadata(PARAM_ARGS_METADATA, target.constructor, key) || {}; 37 | const hasParamData = isNil(data) || isString(data); 38 | const paramData = hasParamData ? data : undefined; 39 | const paramPipes = hasParamData ? pipes : [data, ...pipes]; 40 | 41 | Reflect.defineMetadata( 42 | PARAM_ARGS_METADATA, 43 | assignMetadata(args, paramtype, index, paramData, ...paramPipes), 44 | target.constructor, 45 | key, 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "chore", 5 | "section": "🧹 Chore", 6 | "hidden": false 7 | }, 8 | { 9 | "type": "types", 10 | "section": "💙 Types", 11 | "hidden": false 12 | }, 13 | { 14 | "type": "revert", 15 | "section": "♻ Reverts", 16 | "hidden": false 17 | }, 18 | { 19 | "type": "feat", 20 | "section": "🚀 Features", 21 | "hidden": false 22 | }, 23 | { 24 | "type": "fix", 25 | "section": "🐛 Bug Fixes", 26 | "hidden": false 27 | }, 28 | { 29 | "type": "improvement", 30 | "section": "🌟 Feature Improvements", 31 | "hidden": false 32 | }, 33 | { 34 | "type": "docs", 35 | "section": "📖 Documentation", 36 | "hidden": false 37 | }, 38 | { 39 | "type": "style", 40 | "section": "☯ Styling", 41 | "hidden": false 42 | }, 43 | { 44 | "type": "refactor", 45 | "section": "🔧 Code Refactoring", 46 | "hidden": false 47 | }, 48 | { 49 | "type": "perf", 50 | "section": "💪 Performance Improvements", 51 | "hidden": false 52 | }, 53 | { 54 | "type": "test", 55 | "section": "🐱‍💻 Tests", 56 | "hidden": false 57 | }, 58 | { 59 | "type": "build", 60 | "section": "🔨 Build System", 61 | "hidden": false 62 | }, 63 | { 64 | "type": "ci", 65 | "section": "🛠️ CI", 66 | "hidden": false 67 | } 68 | ] 69 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-vk", 3 | "version": "4.6.0", 4 | "description": "VK-IO module for NestJS", 5 | "homepage": "https://github.com/xTCry/nestjs-vk#readme", 6 | "bugs": { 7 | "url": "https://github.com/xTCry/nestjs-vk/issues" 8 | }, 9 | "repository": "git@github.com:xTCry/nestjs-vk.git", 10 | "license": "MIT", 11 | "author": "xTCry [Vladislav Kh] (https://github.com/xTCry/)", 12 | "main": "./dist/index.js", 13 | "types": "./dist/index.d.ts", 14 | "scripts": { 15 | "build": "rimraf dist && tsc -p tsconfig.json", 16 | "prepublishOnly": "npm run build", 17 | "publish:npm": "npm publish --access public", 18 | "release": "standard-version", 19 | "release:minor": "standard-version --release-as minor", 20 | "release:major": "standard-version --release-as major", 21 | "release:patch": "standard-version --release-as patch", 22 | "release:rc": "standard-version --prerelease rc" 23 | }, 24 | "dependencies": { 25 | "@vk-io/hear": "^1.1.0", 26 | "@vk-io/scenes": "^1.1.0", 27 | "@vk-io/session": "^2.1.0", 28 | "lodash": "^4.17.21", 29 | "rimraf": "^3.0.2", 30 | "standard-version": "^9.3.2" 31 | }, 32 | "devDependencies": { 33 | "@nestjs/common": "^9.0.0", 34 | "@nestjs/core": "^9.0.0", 35 | "@types/lodash": "4.14.195", 36 | "middleware-io": "^2.8.1", 37 | "prettier": "3.0.3", 38 | "reflect-metadata": "0.1.13", 39 | "rxjs": "^7.5.5", 40 | "typescript": "4.3.5", 41 | "vk-io": "^4.8.3" 42 | }, 43 | "peerDependencies": { 44 | "@nestjs/common": "^6.7.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", 45 | "@nestjs/core": "^6.7.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", 46 | "reflect-metadata": "^0.1.13", 47 | "rxjs": "^6.0.0 || ^7.2.0", 48 | "vk-io": "^4.0.0" 49 | }, 50 | "keywords": [ 51 | "nest", 52 | "nestjs", 53 | "nestjs-module", 54 | "vk", 55 | "vk-io", 56 | "vk bot", 57 | "vk bot api", 58 | "bot", 59 | "bot api", 60 | "bot framework" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/bot-partial/scenes/best.scene.ts: -------------------------------------------------------------------------------- 1 | import { IStepContext } from '@vk-io/scenes'; 2 | import { Scene, AddStep, Context, SceneEnter, SceneLeave } from 'nestjs-vk'; 3 | import { BestSceneContextState } from 'src/interfaces/best-scene-context-state'; 4 | // import { Context } from '../../interfaces/context.interface'; 5 | 6 | @Scene('BEST_SCEN') 7 | export class BestScene { 8 | constructor() { 9 | console.log('[BestScene] ctr'); 10 | } 11 | 12 | @SceneEnter() 13 | async onSceneEnter() { 14 | console.log('Wait Enter to scene...'); 15 | 16 | await new Promise((r) => setTimeout(r, 5e3)); 17 | 18 | console.log('Enter to scene'); 19 | } 20 | 21 | @SceneLeave() 22 | onSceneLeave() { 23 | console.log('Leave from scene'); 24 | } 25 | 26 | @AddStep(10) 27 | step10(@Context() context: IStepContext) { 28 | if (context.scene.step.firstTime || !context.text) { 29 | return context.send('any for exit or gg for repeat'); 30 | } 31 | 32 | if (context.text === 'gg') { 33 | return context.scene.step.go(0); 34 | } 35 | 36 | return context.scene.step.next(); 37 | } 38 | 39 | @AddStep() 40 | step1(@Context() context: IStepContext) { 41 | if (context.scene.step.firstTime || !context.text) { 42 | return context.send("What's your name?"); 43 | } 44 | 45 | context.scene.state.firstName = context.text; 46 | 47 | return context.scene.step.next(); 48 | } 49 | 50 | @AddStep() 51 | step2(@Context() context: IStepContext) { 52 | if (context.scene.step.firstTime || !context.text) { 53 | return context.send('How old are you?'); 54 | } 55 | 56 | context.scene.state.age = context.text; 57 | 58 | return context.scene.step.next(); 59 | } 60 | 61 | @AddStep() 62 | async step3(@Context() context: IStepContext) { 63 | const { firstName, age } = context.scene.state; 64 | 65 | await context.send(`👤 ${firstName} ${age} ages`); 66 | 67 | return context.scene.step.next(); // Automatic exit, since this is the last scene 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sample/01-complete-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-complete-app", 3 | "version": "2.0.0", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^9.0.0", 24 | "@nestjs/core": "^9.0.0", 25 | "@nestjs/platform-express": "^9.0.0", 26 | "dotenv": "^16.0.3", 27 | "nestjs-vk": "^4.5.0", 28 | "reflect-metadata": "^0.1.13", 29 | "rxjs": "^7.2.0", 30 | "vk-io": "^4.8.1" 31 | }, 32 | "devDependencies": { 33 | "@nestjs/cli": "^9.0.0", 34 | "@nestjs/schematics": "^9.0.0", 35 | "@nestjs/testing": "^9.0.0", 36 | "@types/express": "^4.17.13", 37 | "@types/jest": "29.2.4", 38 | "@types/node": "18.11.18", 39 | "@types/supertest": "^2.0.11", 40 | "@typescript-eslint/eslint-plugin": "^5.0.0", 41 | "@typescript-eslint/parser": "^5.0.0", 42 | "eslint": "^8.0.1", 43 | "eslint-config-prettier": "^8.3.0", 44 | "eslint-plugin-prettier": "^4.0.0", 45 | "jest": "29.3.1", 46 | "prettier": "^2.3.2", 47 | "source-map-support": "^0.5.20", 48 | "supertest": "^6.1.3", 49 | "ts-jest": "29.0.3", 50 | "ts-loader": "^9.2.3", 51 | "ts-node": "^10.0.0", 52 | "tsconfig-paths": "4.1.1", 53 | "typescript": "^4.7.4" 54 | }, 55 | "jest": { 56 | "moduleFileExtensions": [ 57 | "js", 58 | "json", 59 | "ts" 60 | ], 61 | "rootDir": "src", 62 | "testRegex": ".*\\.spec\\.ts$", 63 | "transform": { 64 | "^.+\\.(t|j)s$": "ts-jest" 65 | }, 66 | "collectCoverageFrom": [ 67 | "**/*.(t|j)s" 68 | ], 69 | "coverageDirectory": "../coverage", 70 | "testEnvironment": "node" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sample/02-multibots-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-complete-app", 3 | "version": "2.0.0", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^9.0.0", 24 | "@nestjs/core": "^9.0.0", 25 | "@nestjs/platform-express": "^9.0.0", 26 | "dotenv": "^16.0.3", 27 | "nestjs-vk": "^4.5.0", 28 | "reflect-metadata": "^0.1.13", 29 | "rxjs": "^7.2.0", 30 | "vk-io": "^4.8.1" 31 | }, 32 | "devDependencies": { 33 | "@nestjs/cli": "^9.0.0", 34 | "@nestjs/schematics": "^9.0.0", 35 | "@nestjs/testing": "^9.0.0", 36 | "@types/express": "^4.17.13", 37 | "@types/jest": "29.2.4", 38 | "@types/node": "18.11.18", 39 | "@types/supertest": "^2.0.11", 40 | "@typescript-eslint/eslint-plugin": "^5.0.0", 41 | "@typescript-eslint/parser": "^5.0.0", 42 | "eslint": "^8.0.1", 43 | "eslint-config-prettier": "^8.3.0", 44 | "eslint-plugin-prettier": "^4.0.0", 45 | "jest": "29.3.1", 46 | "prettier": "^2.3.2", 47 | "source-map-support": "^0.5.20", 48 | "supertest": "^6.1.3", 49 | "ts-jest": "29.0.3", 50 | "ts-loader": "^9.2.3", 51 | "ts-node": "^10.0.0", 52 | "tsconfig-paths": "4.1.1", 53 | "typescript": "^4.7.4" 54 | }, 55 | "jest": { 56 | "moduleFileExtensions": [ 57 | "js", 58 | "json", 59 | "ts" 60 | ], 61 | "rootDir": "src", 62 | "testRegex": ".*\\.spec\\.ts$", 63 | "transform": { 64 | "^.+\\.(t|j)s$": "ts-jest" 65 | }, 66 | "collectCoverageFrom": [ 67 | "**/*.(t|j)s" 68 | ], 69 | "coverageDirectory": "../coverage", 70 | "testEnvironment": "node" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sample/01-complete-app/src/echo/echo.update.ts: -------------------------------------------------------------------------------- 1 | import { UseFilters, UseGuards } from '@nestjs/common'; 2 | import { 3 | InjectVkApi, 4 | Update, 5 | On, 6 | Ctx, 7 | Next, 8 | Message, 9 | Hears, 10 | HearFallback, 11 | } from 'nestjs-vk'; 12 | import { MessageContext, VK } from 'vk-io'; 13 | import { NextMiddleware } from 'middleware-io'; 14 | 15 | import { EchoService } from './echo.service'; 16 | import { Context } from '../interfaces/context.interface'; 17 | import { VkExceptionFilter, AdminGuard, ReverseTextPipe } from '../common'; 18 | 19 | @Update() 20 | @UseFilters(VkExceptionFilter) 21 | export class EchoUpdate { 22 | public groupId: number; 23 | 24 | constructor( 25 | @InjectVkApi() 26 | private readonly bot: VK, 27 | private readonly echoService: EchoService, 28 | ) {} 29 | 30 | async onModuleInit() { 31 | try { 32 | const [group] = await this.bot.api.groups.getById({}); 33 | 34 | this.groupId = group.id; 35 | } catch (err) { 36 | console.error(err); 37 | } 38 | } 39 | 40 | @On('message') 41 | onMessageFirst(@Ctx() ctx: Context, @Next() next: NextMiddleware) { 42 | if (ctx.is(['message'])) { 43 | // Skip messages 44 | if (ctx.isOutbox || ctx.isFromGroup) { 45 | return; 46 | } 47 | } 48 | 49 | next(); 50 | } 51 | 52 | @On('message_new', (ctx, next) => { 53 | console.log('ctx session:', ctx.session); 54 | ctx.session.cc = (ctx.session.cc || 0) + 1; 55 | next(); 56 | }) 57 | async onNewMessageSecond(@Next() next: NextMiddleware) { 58 | try { 59 | await next(); 60 | } catch (err) { 61 | console.log('[onNewMessageSecond] Catched exception', err); 62 | } 63 | } 64 | 65 | @Hears(/^\/?(start|старт)$/i) 66 | async onStartCommand(@Next() next: NextMiddleware) { 67 | /* for test */ next(); 68 | return `Hey`; 69 | } 70 | 71 | @Hears(['/sub', 'чек']) 72 | async onSubscriberCommand(@Ctx() ctx: MessageContext) { 73 | const isSib = await this.bot.api.groups.isMember({ 74 | group_id: String(this.groupId), 75 | user_id: ctx.senderId, 76 | }); 77 | return isSib ? 'So good!' : 'No sub'; 78 | } 79 | 80 | @Hears('/admin') 81 | @UseGuards(AdminGuard) 82 | onAdminCommand(@Ctx() ctx: MessageContext) { 83 | ctx.send({ sticker_id: 5 }); 84 | } 85 | 86 | @HearFallback() 87 | onHearFallback( 88 | @Ctx() ctx: MessageContext, 89 | @Message('text', new ReverseTextPipe()) reversedText: string, 90 | ) { 91 | if (reversedText) { 92 | return this.echoService.echo(reversedText); 93 | } else if (ctx.hasAttachments('sticker')) { 94 | ctx.send({ sticker_id: ctx.getAttachments('sticker')[0].id % 24 }); 95 | return; 96 | } 97 | 98 | return 'What?..'; 99 | } 100 | 101 | @On('message_new') 102 | async onMessageThird(@Ctx() ctx: Context, @Next() next: NextMiddleware) { 103 | if (!ctx.session.isAuth) { 104 | ctx.session.isAuth = true; 105 | return 'Send any message'; 106 | } 107 | await next(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/vk-core.module.ts: -------------------------------------------------------------------------------- 1 | import { DiscoveryModule, ModuleRef } from '@nestjs/core'; 2 | import { DynamicModule, Global, Inject, Module, OnApplicationShutdown, Provider, Type } from '@nestjs/common'; 3 | import { VK } from 'vk-io'; 4 | 5 | import { VkModuleAsyncOptions, VkModuleOptions, VkOptionsFactory } from './interfaces'; 6 | import { VK_API_NAME, VK_MODULE_OPTIONS } from './vk.constants'; 7 | import { ListenersExplorerService, MetadataAccessorService } from './services'; 8 | import { createVkApiFactory, getVkApiToken } from './utils'; 9 | 10 | @Global() 11 | @Module({ 12 | imports: [DiscoveryModule], 13 | providers: [ListenersExplorerService, MetadataAccessorService], 14 | }) 15 | export class VkCoreModule implements OnApplicationShutdown { 16 | constructor( 17 | @Inject(VK_API_NAME) 18 | private readonly vkApiName: string, 19 | private readonly moduleRef: ModuleRef, 20 | ) {} 21 | 22 | public static forRoot(options: VkModuleOptions): DynamicModule { 23 | const vkApiName = getVkApiToken(options.vkName); 24 | 25 | const vkApiNameProvider = { 26 | provide: VK_API_NAME, 27 | useValue: vkApiName, 28 | }; 29 | 30 | const vkApiProvider: Provider = { 31 | provide: vkApiName, 32 | useFactory: async () => await createVkApiFactory(options), 33 | }; 34 | 35 | const providers = [vkApiNameProvider, vkApiProvider]; 36 | 37 | return { 38 | module: VkCoreModule, 39 | providers: [ 40 | { 41 | provide: VK_MODULE_OPTIONS, 42 | useValue: options, 43 | }, 44 | ...providers, 45 | ], 46 | exports: [...providers], 47 | }; 48 | } 49 | 50 | public static forRootAsync(options: VkModuleAsyncOptions): DynamicModule { 51 | const vkApiName = getVkApiToken(options.vkName); 52 | 53 | const vkApiNameProvider = { 54 | provide: VK_API_NAME, 55 | useValue: vkApiName, 56 | }; 57 | 58 | const vkApiProvider: Provider = { 59 | provide: vkApiName, 60 | useFactory: async (options: VkModuleOptions) => await createVkApiFactory(options), 61 | inject: [VK_MODULE_OPTIONS], 62 | }; 63 | 64 | const asyncProviders = this.createAsyncProviders(options); 65 | const providers = [vkApiNameProvider, vkApiProvider]; 66 | 67 | return { 68 | module: VkCoreModule, 69 | imports: options.imports, 70 | providers: [...asyncProviders, ...providers], 71 | exports: [...providers], 72 | }; 73 | } 74 | 75 | async onApplicationShutdown(): Promise { 76 | const vk = this.moduleRef.get(this.vkApiName); 77 | vk && (await vk.updates.stop()); 78 | } 79 | 80 | private static createAsyncProviders(options: VkModuleAsyncOptions): Provider[] { 81 | if (options.useExisting || options.useFactory) { 82 | return [this.createAsyncOptionsProvider(options)]; 83 | } 84 | const useClass = options.useClass as Type; 85 | return [ 86 | this.createAsyncOptionsProvider(options), 87 | { 88 | provide: useClass, 89 | useClass, 90 | }, 91 | ]; 92 | } 93 | 94 | private static createAsyncOptionsProvider(options: VkModuleAsyncOptions): Provider { 95 | if (options.useFactory) { 96 | return { 97 | provide: VK_MODULE_OPTIONS, 98 | useFactory: options.useFactory, 99 | inject: options.inject || [], 100 | }; 101 | } 102 | 103 | // `as Type` is a workaround for microsoft/TypeScript#31603 104 | const inject = [(options.useClass || options.useExisting) as Type]; 105 | return { 106 | provide: VK_MODULE_OPTIONS, 107 | useFactory: async (optionsFactory: VkOptionsFactory) => await optionsFactory.createVkOptions(), 108 | inject, 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /sample/02-multibots-app/src/bot-partial/bot-partial.update.ts: -------------------------------------------------------------------------------- 1 | import { Inject, UseFilters, UseGuards } from '@nestjs/common'; 2 | import { 3 | InjectVkApi, 4 | Update, 5 | On, 6 | Ctx, 7 | Next, 8 | Hears, 9 | HearFallback, 10 | VK_HEAR_MANAGER, 11 | VK_SESSION_MANAGER, 12 | VK_SCENE_MANAGER, 13 | } from 'nestjs-vk'; 14 | import { getRandomId, MessageContext, VK } from 'vk-io'; 15 | import { HearManager } from '@vk-io/hear'; 16 | import { SessionManager } from '@vk-io/session'; 17 | import { SceneManager, StepScene } from '@vk-io/scenes'; 18 | import { NextMiddleware } from 'middleware-io'; 19 | 20 | import { Context } from '../interfaces/context.interface'; 21 | import { VkExceptionFilter, AdminGuard } from '../common'; 22 | import { BestSceneContextState } from 'src/interfaces/best-scene-context-state'; 23 | 24 | @Update() 25 | @UseFilters(VkExceptionFilter) 26 | export class BotPartialUpdate { 27 | constructor( 28 | @InjectVkApi('firstBot') 29 | private readonly firstBot: VK, 30 | @InjectVkApi('secondBot') 31 | private readonly secondBot: VK, 32 | @Inject(VK_HEAR_MANAGER) 33 | private readonly hearManagerProvider: HearManager, 34 | @Inject(VK_SESSION_MANAGER) 35 | private readonly sessionManagerProvider: SessionManager, 36 | @Inject(VK_SCENE_MANAGER) 37 | private readonly sceneManager: SceneManager, 38 | ) { 39 | this.hearManagerProvider 40 | .hear('qq', (ctx) => { 41 | ctx.send('HelloW'); 42 | }) 43 | .hear('ww', (ctx) => { 44 | return ctx.scene.enter('signup'); 45 | }) 46 | .hear('ff', (ctx) => { 47 | return ctx.scene.enter('BEST_SCEN'); 48 | }); 49 | 50 | this.sceneManager.addScenes([ 51 | new StepScene<{}, BestSceneContextState>('signup', [ 52 | (context) => { 53 | if (context.scene.step.firstTime || !context.text) { 54 | return context.send("What's your name?"); 55 | } 56 | 57 | context.scene.state.firstName = context.text; 58 | 59 | return context.scene.step.next(); 60 | }, 61 | (context) => { 62 | if (context.scene.step.firstTime || !context.text) { 63 | return context.send('How old are you?'); 64 | } 65 | 66 | context.scene.state.age = context.text; 67 | 68 | return context.scene.step.next(); 69 | }, 70 | async (context) => { 71 | const { firstName, age } = context.scene.state; 72 | 73 | await context.send(`👤 ${firstName} ${age} ages`); 74 | 75 | return context.scene.step.next(); // Automatic exit, since this is the last scene 76 | }, 77 | ]), 78 | ]); 79 | } 80 | 81 | @On('message') 82 | async onMessageFirst(@Ctx() ctx: Context, @Next() next: NextMiddleware) { 83 | if (ctx.is(['message'])) { 84 | // Skip messages 85 | if (ctx.isOutbox || ctx.isFromGroup) { 86 | return; 87 | } 88 | } 89 | 90 | try { 91 | await next(); 92 | } catch (err) { 93 | console.log('[onNewMessageSecond] Catched exception', err); 94 | } 95 | } 96 | 97 | @Hears('/info') 98 | onInfoCommand(@Ctx() ctx: MessageContext) { 99 | const message = ` 100 | Info: 101 | • hearManagerProvider.length: ${this.hearManagerProvider.length} 102 | • sessionManagerProvider.length: ${JSON.stringify( 103 | this.sessionManagerProvider, 104 | ).slice(0, 150)} 105 | `; 106 | return message; 107 | } 108 | 109 | @Hears(/^\/?(start|старт)$/i) 110 | async onStartCommand(@Next() next: NextMiddleware) { 111 | /* for test */ next(); 112 | return `Hey`; 113 | } 114 | 115 | @Hears('/admin') 116 | @UseGuards(AdminGuard) 117 | onAdminCommand(@Ctx() ctx: MessageContext) { 118 | ctx.send({ sticker_id: 5 }); 119 | } 120 | 121 | @Hears('/all') 122 | async onAllCommand(@Ctx() ctx: MessageContext) { 123 | const message = `HelloW, user. (${ctx.createdAt})`; 124 | await this.firstBot.api.messages.send({ 125 | peer_ids: ctx.peerId, 126 | message: `${message} [1]`, 127 | random_id: getRandomId(), 128 | }); 129 | 130 | await new Promise((r) => setTimeout(r, 2e3)); 131 | 132 | await this.secondBot.api.messages.send({ 133 | peer_ids: ctx.peerId, 134 | message: `${message} [2]`, 135 | random_id: getRandomId(), 136 | }); 137 | } 138 | 139 | @HearFallback() 140 | onHearFallback(@Ctx() ctx: MessageContext) { 141 | if (ctx.hasAttachments('sticker')) { 142 | ctx.send({ sticker_id: ctx.getAttachments('sticker')[0].id % 24 }); 143 | return; 144 | } 145 | 146 | return 'What?..'; 147 | } 148 | 149 | @On('message_new') 150 | async onMessageAuth(@Ctx() ctx: Context, @Next() next: NextMiddleware) { 151 | if (!1 && !ctx.session.isAuth) { 152 | if (!ctx.is(['message'])) { 153 | return 'Need auth! Send any text message'; 154 | } 155 | 156 | if (!ctx.session.code) { 157 | const code = Math.floor(Math.random() * 1e6) 158 | .toString() 159 | .padEnd(4, '0'); 160 | ctx.session.code = code; 161 | return `Send code '${code}' for auth`; 162 | } else if (ctx.session.code !== (ctx as MessageContext).text) { 163 | return 'Wrong code! Try now'; 164 | } 165 | 166 | ctx.session.isAuth = true; 167 | return 'Success authorized!'; 168 | } 169 | 170 | // and next (hearMiddleware, ...) 171 | await next(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [4.6.0](https://github.com/xTCry/nestjs-vk/compare/v4.5.1...v4.6.0) (2023-09-05) 6 | 7 | 8 | ### 🚀 Features 9 | 10 | * **providers:** separate configuration of providers managers in module ([ad09376](https://github.com/xTCry/nestjs-vk/commit/ad09376896b4ddde967bc2943e2ea1c34c6a59dd)) 11 | 12 | ### [4.5.1](https://github.com/xTCry/nestjs-vk/compare/v4.5.0...v4.5.1) (2023-09-05) 13 | 14 | 15 | ### 🐛 Bug Fixes 16 | 17 | * **type:** simple.scene.ts ([799579b](https://github.com/xTCry/nestjs-vk/commit/799579b3f6363adb1a26c0bcc9b101acae05795c)) 18 | * **typo:** return .env in .gitignore ([d69d8e0](https://github.com/xTCry/nestjs-vk/commit/d69d8e0c677678c0469e1ad35f7bc59375ac2804)) 19 | 20 | 21 | ### 🚀 Features 22 | 23 | * **sample:** update deps 01-complete-app ([6ff0f2a](https://github.com/xTCry/nestjs-vk/commit/6ff0f2a9e639d8c246d94f850f40a497982ab96c)) 24 | * **sample:** update deps 02-complete-app ([f702be7](https://github.com/xTCry/nestjs-vk/commit/f702be75e7d0bc407c788f312f5e6697a0b0820d)) 25 | 26 | 27 | ### 🧹 Chore 28 | 29 | * **decorators:** remove type `ParameterDecorator` from `InjectVkApi` ([92544d1](https://github.com/xTCry/nestjs-vk/commit/92544d17709cec13053f4a78775dbec54d958d70)) 30 | * **deps:** update ([ee30a41](https://github.com/xTCry/nestjs-vk/commit/ee30a4159382fe0c5ef76838c9af11c045987153)) 31 | 32 | ## [4.5.0](https://github.com/xTCry/nestjs-vk/compare/v4.5.0-rc.0...v4.5.0) (2023-01-27) 33 | 34 | 35 | ### 🚀 Features 36 | 37 | * **docs:** add scene support ([000a11c](https://github.com/xTCry/nestjs-vk/commit/000a11cf14d028ae9f40dbea9cc2950a0d2b1b46)) 38 | 39 | ## [4.5.0-rc.0](https://github.com/xTCry/nestjs-vk/compare/v4.4.2...v4.5.0-rc.0) (2022-03-19) 40 | 41 | 42 | ### 🧹 Chore 43 | 44 | * **deps:** up `nestjs` version to 8 ([ffe50cb](https://github.com/xTCry/nestjs-vk/commit/ffe50cb83bba6afb20c7c4393973be34f75d1068)) 45 | * **deps:** updated `vk-io` ([e3f06ef](https://github.com/xTCry/nestjs-vk/commit/e3f06ef9a3a06059d7897499acdec11dd371f75d)) 46 | * **providers:** changed to `useFactory` ([de7f02e](https://github.com/xTCry/nestjs-vk/commit/de7f02eff1ee4ee32cb18d4475cfe8d92c448e0a)) 47 | 48 | 49 | ### 🐛 Bug Fixes 50 | 51 | * **execution-context:** fixed return value of `getNext` ([f97e5cc](https://github.com/xTCry/nestjs-vk/commit/f97e5cc6aff70b9f398b12ca2534d675f52ee65a)) 52 | * **typos:** fixed typos in file name ([1367b8d](https://github.com/xTCry/nestjs-vk/commit/1367b8de9a6ccc3a85b856d12093a15a6742335b)) 53 | 54 | 55 | ### 🚀 Features 56 | 57 | * **decorators:** added `Use` decorator for `vk.updates` ([8c615d0](https://github.com/xTCry/nestjs-vk/commit/8c615d0a5484b8bd834c9830781d83f1a9ed3ca0)) 58 | 59 | ### 4.4.2 (2022-02-25) 60 | 61 | 62 | ### 🔧 Code Refactoring 63 | 64 | * **lib:** changed veriables and decorators name ([6a0c209](https://github.com/xTCry/nestjs-vk/commit/6a0c2091998d79f38c25b95b9d16e87ab10f3f1b)) 65 | 66 | 67 | ### 🐛 Bug Fixes 68 | 69 | * **decorator:** fixed undefine getter ([49e78b6](https://github.com/xTCry/nestjs-vk/commit/49e78b61a0a7ab57c79d8b31e5e3e12747ebf25f)) 70 | * fuck vscode ([96d490d](https://github.com/xTCry/nestjs-vk/commit/96d490dba7838570d735306ca56cc21d92ec9b03)) 71 | * **typo:** notReplyMessage ([b7d87d0](https://github.com/xTCry/nestjs-vk/commit/b7d87d033dd0458e025d4d7abc952c66fd9098dc)) 72 | 73 | 74 | ### 🚀 Features 75 | 76 | * **docs:** added readme ([2e6d4ce](https://github.com/xTCry/nestjs-vk/commit/2e6d4cef8b9be3ad89dc5b1f248dfe2d1e0a8136)) 77 | * init repos ([3663988](https://github.com/xTCry/nestjs-vk/commit/3663988744ba8a3addc6700415aeabd5d29cde35)) 78 | * **package:** added standard-version ([b2905a5](https://github.com/xTCry/nestjs-vk/commit/b2905a538da41b6c01a5e1a2a9f0f0df13a41ce2)) 79 | * **sample:** added sample ([a6f2829](https://github.com/xTCry/nestjs-vk/commit/a6f2829dd8e944702d7c64051ce010f1fed481cc)) 80 | * **sample:** added sample multibots ([d218509](https://github.com/xTCry/nestjs-vk/commit/d2185090c64d83bcbd7ac6bb3a4b7d9e02359830)) 81 | * **scene:** added scenes support ([c25e0be](https://github.com/xTCry/nestjs-vk/commit/c25e0be142a6d63845e23881e5f37e3b5edde157)) 82 | 83 | 84 | ### 🧹 Chore 85 | 86 | * **listeners:** not cancel hear if false `useHearManager` ([593baef](https://github.com/xTCry/nestjs-vk/commit/593baefda96dc94806724a912047906790d8e8a8)) 87 | * **package.json:** update ([17fbc48](https://github.com/xTCry/nestjs-vk/commit/17fbc48f6310af4416512babf25d62efe7d0b6fe)) 88 | * **package:** updated deps ([e862473](https://github.com/xTCry/nestjs-vk/commit/e862473252087bb0855e78f52c3b2810c8bc7f3b)) 89 | * **release:** 4.4.0 ([5350ab3](https://github.com/xTCry/nestjs-vk/commit/5350ab3d66ef99cfacacc20a180c36bf704e2c3d)) 90 | 91 | ## 4.4.0 (2022-02-25) 92 | 93 | 94 | ### 🔧 Code Refactoring 95 | 96 | * **lib:** changed veriables and decorators name ([6a0c209](https://github.com/xTCry/nestjs-vk/commit/6a0c2091998d79f38c25b95b9d16e87ab10f3f1b)) 97 | 98 | 99 | ### 🐛 Bug Fixes 100 | 101 | * **decorator:** fixed undefine getter ([49e78b6](https://github.com/xTCry/nestjs-vk/commit/49e78b61a0a7ab57c79d8b31e5e3e12747ebf25f)) 102 | * fuck vscode ([96d490d](https://github.com/xTCry/nestjs-vk/commit/96d490dba7838570d735306ca56cc21d92ec9b03)) 103 | * **typo:** notReplyMessage ([b7d87d0](https://github.com/xTCry/nestjs-vk/commit/b7d87d033dd0458e025d4d7abc952c66fd9098dc)) 104 | 105 | 106 | ### 🧹 Chore 107 | 108 | * **listeners:** not cancel hear if false `useHearManager` ([593baef](https://github.com/xTCry/nestjs-vk/commit/593baefda96dc94806724a912047906790d8e8a8)) 109 | * **package.json:** update ([17fbc48](https://github.com/xTCry/nestjs-vk/commit/17fbc48f6310af4416512babf25d62efe7d0b6fe)) 110 | * **package:** updated deps ([e862473](https://github.com/xTCry/nestjs-vk/commit/e862473252087bb0855e78f52c3b2810c8bc7f3b)) 111 | 112 | 113 | ### 🚀 Features 114 | 115 | * **docs:** added readme ([2e6d4ce](https://github.com/xTCry/nestjs-vk/commit/2e6d4cef8b9be3ad89dc5b1f248dfe2d1e0a8136)) 116 | * init repos ([3663988](https://github.com/xTCry/nestjs-vk/commit/3663988744ba8a3addc6700415aeabd5d29cde35)) 117 | * **package:** added standard-version ([b2905a5](https://github.com/xTCry/nestjs-vk/commit/b2905a538da41b6c01a5e1a2a9f0f0df13a41ce2)) 118 | * **sample:** added sample ([a6f2829](https://github.com/xTCry/nestjs-vk/commit/a6f2829dd8e944702d7c64051ce010f1fed481cc)) 119 | * **sample:** added sample multibots ([d218509](https://github.com/xTCry/nestjs-vk/commit/d2185090c64d83bcbd7ac6bb3a4b7d9e02359830)) 120 | * **scene:** added scenes support ([c25e0be](https://github.com/xTCry/nestjs-vk/commit/c25e0be142a6d63845e23881e5f37e3b5edde157)) 121 | 122 | # 4.4.0-rc.2 (2022-02-25) 123 | 124 | 125 | ### Bug Fixes 126 | 127 | * fuck vscode ([96d490d](https://github.com/xTCry/nestjs-vk/commit/96d490dba7838570d735306ca56cc21d92ec9b03)) 128 | * **typo:** notReplyMessage ([b7d87d0](https://github.com/xTCry/nestjs-vk/commit/b7d87d033dd0458e025d4d7abc952c66fd9098dc)) 129 | 130 | 131 | ### Features 132 | 133 | * **docs:** added readme ([2e6d4ce](https://github.com/xTCry/nestjs-vk/commit/2e6d4cef8b9be3ad89dc5b1f248dfe2d1e0a8136)) 134 | * init repos ([3663988](https://github.com/xTCry/nestjs-vk/commit/3663988744ba8a3addc6700415aeabd5d29cde35)) 135 | * **sample:** added sample ([a6f2829](https://github.com/xTCry/nestjs-vk/commit/a6f2829dd8e944702d7c64051ce010f1fed481cc)) 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NestJS VK 2 | 3 | [![npm](https://img.shields.io/npm/v/nestjs-vk.svg?style=flat-square)](https://www.npmjs.com/package/nestjs-vk) 4 | [![NPM](https://img.shields.io/npm/dt/nestjs-vk.svg?style=flat-square)](https://www.npmjs.com/package/nestjs-vk) 5 | [![GitHub last commit](https://img.shields.io/github/last-commit/xtcry/nestjs-vk)](https://github.com/xtcry/nestjs-vk) 6 | 7 | 8 | 9 | **NestJS VK** – powerful solution for creating VK bots. 10 | 11 | This package uses the best of the NodeJS world under the hood. [VK-IO](https://github.com/negezor/vk-io) is a powerful Node.js module that allows you to easily interact with the VK API and for creating bots, and [NestJS](https://github.com/nestjs) is a progressive framework for creating well-architectured applications. 12 | 13 | This module provides a quick and easy way to interact with the VK API and create VK bots and deep integration with your NestJS application. 14 | 15 | **Features** 16 | 17 | - Simple. Easy to use. 18 | - Ability to create custom decorators. 19 | - Scenes support. 20 | - Ability to run multiple bots simultaneously. 21 | - Full support of NestJS guards, interceptors, filters and pipes! 22 | 23 | ## Installation 24 | 25 | #### NPM 26 | 27 | ```bash 28 | $ npm i nestjs-vk vk-io 29 | ``` 30 | 31 | #### Yarn 32 | 33 | ```bash 34 | $ yarn add nestjs-vk vk-io 35 | ``` 36 | 37 | ## Usage 38 | 39 | Once the installation process is complete, we can import the `VkModule` into the root `AppModule`: 40 | 41 | ```typescript 42 | import { Module } from '@nestjs/common'; 43 | import { VkModule } from 'nestjs-vk'; 44 | 45 | import { AppUpdate } from './app.update'; 46 | import { SimpleScene } from './scene/simple.scene'; 47 | 48 | @Module({ 49 | imports: [ 50 | VkModule.forManagers({ 51 | useSessionManager: false, 52 | useSceneManager: false, 53 | useHearManager: false, 54 | }), 55 | VkModule.forRootAsync({ 56 | inject: [MainMiddleware], 57 | useFactory: async (mainMiddleware: MainMiddleware) => ({ 58 | token: process.env.VK_BOT_TOKEN, 59 | options: { 60 | pollingGroupId: +process.env.VK_BOT_GROUP_ID, 61 | apiMode: 'sequential', 62 | }, 63 | // launchOptions: false, 64 | // notReplyMessage: true, 65 | middlewaresBefore: [mainMiddleware.middlewaresBefore], 66 | middlewaresAfter: [mainMiddleware.middlewaresAfter], 67 | }), 68 | }), 69 | // VkModule.forRoot({ 70 | // token: process.env.VK_BOT_TOKEN, 71 | // options: { 72 | // pollingGroupId: +process.env.VK_BOT_GROUP_ID, 73 | // apiMode: 'sequential', 74 | // }, 75 | // }), 76 | ], 77 | providers: [MainMiddleware, AppUpdate, SimpleScene], 78 | exports: [MainMiddleware], 79 | }) 80 | export class AppModule {} 81 | ``` 82 | 83 | 84 | Main middleware `main.middleware.ts`: 85 | 86 | ```typescript 87 | import { Inject, Injectable } from '@nestjs/common'; 88 | import { VK_HEAR_MANAGER, VK_SCENE_MANAGER } from 'nestjs-vk'; 89 | import { MessageContext, Context, Composer } from 'vk-io'; 90 | import { HearManager } from '@vk-io/hear'; 91 | import { SessionManager } from '@vk-io/session'; 92 | import { SceneManager } from '@vk-io/scenes'; 93 | 94 | @Injectable() 95 | export class MainMiddleware { 96 | private readonly sessionManager: SessionManager; 97 | @Inject(VK_HEAR_MANAGER) 98 | private readonly hearManagerProvider: HearManager; 99 | @Inject(VK_SCENE_MANAGER) 100 | private readonly sceneManager: SceneManager; 101 | 102 | constructor() { 103 | this.sessionManager = new SessionManager({ 104 | // ... 105 | }); 106 | } 107 | 108 | get middlewaresBefore() { 109 | const composer = Composer.builder(); 110 | 111 | composer.use(this.sessionManager.middleware); 112 | composer.use(this.sceneManager.middleware); 113 | 114 | return composer.compose(); 115 | } 116 | 117 | get middlewaresAfter() { 118 | const composer = Composer.builder(); 119 | 120 | composer.use(this.hearManagerProvider.middleware); 121 | 122 | return composer.compose(); 123 | } 124 | } 125 | ``` 126 | 127 | Then create `app.update.ts` file and add some decorators for handling VK bot API updates: 128 | 129 | ```typescript 130 | import { InjectVkApi, Update, Ctx, Message, Hears, HearFallback } from 'nestjs-vk'; 131 | import { MessageContext, VK } from 'vk-io'; 132 | 133 | import { AppService } from './app.service'; 134 | import { SIMPLE_SCENE } from './vk.constants'; 135 | 136 | @Update() 137 | export class AppUpdate { 138 | public groupId: number; 139 | 140 | constructor( 141 | @InjectVkApi() 142 | private readonly vk: VK, 143 | private readonly appService: AppService, 144 | ) {} 145 | 146 | async onModuleInit() { 147 | try { 148 | const [group] = await this.vk.api.groups.getById({}); 149 | this.groupId = group.id; 150 | } catch (err) { 151 | console.error(err); 152 | } 153 | } 154 | 155 | @Hears(/^\/?(start|старт)$/i) 156 | async onStartCommand(@Ctx() ctx: MessageContext) { 157 | await ctx.reply('Welcome'); 158 | } 159 | 160 | @Hears('hi') 161 | async hearsHi(@Ctx() ctx: MessageContext) { 162 | return 'Hey there'; 163 | } 164 | 165 | @Hears(/scene( ?(?[0-9]+))?$/i) 166 | async hearsScene(@Ctx() ctx: MessageContext) { 167 | const stateNumber = ((e) => (isNaN(Number(e)) ? null : Number(e)))(ctx.$match?.groups?.state); 168 | ctx.scene.enter(SIMPLE_SCENE, { state: { stateNumber } }); 169 | } 170 | 171 | @Hears(['/sub', 'subscriber']) 172 | async onSubscriberCommand(@Ctx() ctx: MessageContext) { 173 | const isSib = await this.vk.api.groups.isMember({ 174 | group_id: String(this.groupId), 175 | user_id: ctx.senderId, 176 | }); 177 | return isSib ? 'So good!' : 'No sub'; 178 | } 179 | 180 | @HearFallback() 181 | onHearFallback(@Ctx() ctx: MessageContext, @Message('text') text: string) { 182 | if (text) { 183 | return this.appService.echo(text); 184 | } else if (ctx.hasAttachments('sticker')) { 185 | ctx.send({ sticker_id: ctx.getAttachments('sticker')[0].id % 24 }); 186 | return; 187 | } 188 | 189 | return 'What?..'; 190 | } 191 | } 192 | ``` 193 | 194 | For a simple scene, let's create an `simple.scene.ts` file and do a few steps for it: 195 | 196 | ```typescript 197 | import { Scene, AddStep, Ctx, SceneEnter, SceneLeave } from 'nestjs-vk'; 198 | import { MessageContext } from 'vk-io'; 199 | import { IStepContext } from '@vk-io/scenes'; 200 | 201 | import { SIMPLE_SCENE } from './vk.constants'; 202 | 203 | @Scene(SIMPLE_SCENE) 204 | export class SimpleScene { 205 | @SceneEnter() 206 | async onSceneEnter(@Ctx() ctx: IStepContext) { 207 | const { stateNumber } = ctx.scene.state; 208 | await ctx.reply( 209 | `Hello! I am a simple scene. Your state number is ${stateNumber}.\n` + 210 | ` You can send me a number and I will multiply it by 2.`, 211 | ); 212 | } 213 | 214 | @SceneLeave() 215 | async onSceneLeave(@Ctx() ctx: IStepContext) { 216 | await ctx.reply('Bye!'); 217 | } 218 | 219 | // @Hears('exit') 220 | // async onHearsExit(@Ctx() ctx: IStepContext) { 221 | // await ctx.scene.leave(); 222 | // } 223 | 224 | @AddStep() 225 | async onAddStep(@Ctx() ctx: IStepContext) { 226 | let { stateNumber: number } = ctx.scene.state; 227 | 228 | if (!ctx.scene.step.firstTime) { 229 | number = Number(ctx.text); 230 | } 231 | 232 | if (ctx.scene.step.firstTime && number === null) { 233 | await ctx.reply('Please send me a number.'); 234 | return; 235 | } 236 | 237 | if (isNaN(number)) { 238 | await ctx.reply('Wrong. Please send me a number.'); 239 | return; 240 | } 241 | 242 | await ctx.reply(`Your number multiplied by 2 is ${number * 2}.`); 243 | 244 | if (number > 20 && number % 2 === 0) { 245 | await ctx.scene.leave(); 246 | } 247 | } 248 | } 249 | ``` 250 | -------------------------------------------------------------------------------- /sample/01-complete-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | 27 | # Mongo Explorer plugin: 28 | .idea/**/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | ### VisualStudio template 53 | ## Ignore Visual Studio temporary files, build results, and 54 | ## files generated by popular Visual Studio add-ons. 55 | ## 56 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 57 | 58 | # User-specific files 59 | *.suo 60 | *.user 61 | *.userosscache 62 | *.sln.docstates 63 | 64 | # User-specific files (MonoDevelop/Xamarin Studio) 65 | *.userprefs 66 | 67 | # Build results 68 | [Dd]ebug/ 69 | [Dd]ebugPublic/ 70 | [Rr]elease/ 71 | [Rr]eleases/ 72 | x64/ 73 | x86/ 74 | bld/ 75 | [Bb]in/ 76 | [Oo]bj/ 77 | [Ll]og/ 78 | 79 | # Visual Studio 2015 cache/options directory 80 | .vs/ 81 | # Uncomment if you have tasks that create the project's static files in wwwroot 82 | #wwwroot/ 83 | 84 | # MSTest test Results 85 | [Tt]est[Rr]esult*/ 86 | [Bb]uild[Ll]og.* 87 | 88 | # NUNIT 89 | *.VisualState.xml 90 | TestResult.xml 91 | 92 | # Build Results of an ATL Project 93 | [Dd]ebugPS/ 94 | [Rr]eleasePS/ 95 | dlldata.c 96 | 97 | # Benchmark Results 98 | BenchmarkDotNet.Artifacts/ 99 | 100 | # .NET Core 101 | project.lock.json 102 | project.fragment.lock.json 103 | artifacts/ 104 | **/Properties/launchSettings.json 105 | 106 | *_i.c 107 | *_p.c 108 | *_i.h 109 | *.ilk 110 | *.meta 111 | *.obj 112 | *.pch 113 | *.pdb 114 | *.pgc 115 | *.pgd 116 | *.rsp 117 | *.sbr 118 | *.tlb 119 | *.tli 120 | *.tlh 121 | *.tmp 122 | *.tmp_proj 123 | *.log 124 | *.vspscc 125 | *.vssscc 126 | .builds 127 | *.pidb 128 | *.svclog 129 | *.scc 130 | 131 | # Chutzpah Test files 132 | _Chutzpah* 133 | 134 | # Visual C++ cache files 135 | ipch/ 136 | *.aps 137 | *.ncb 138 | *.opendb 139 | *.opensdf 140 | *.sdf 141 | *.cachefile 142 | *.VC.db 143 | *.VC.VC.opendb 144 | 145 | # Visual Studio profiler 146 | *.psess 147 | *.vsp 148 | *.vspx 149 | *.sap 150 | 151 | # Visual Studio Trace Files 152 | *.e2e 153 | 154 | # TFS 2012 Local Workspace 155 | $tf/ 156 | 157 | # Guidance Automation Toolkit 158 | *.gpState 159 | 160 | # ReSharper is a .NET coding add-in 161 | _ReSharper*/ 162 | *.[Rr]e[Ss]harper 163 | *.DotSettings.user 164 | 165 | # JustCode is a .NET coding add-in 166 | .JustCode 167 | 168 | # TeamCity is a build add-in 169 | _TeamCity* 170 | 171 | # DotCover is a Code Coverage Tool 172 | *.dotCover 173 | 174 | # AxoCover is a Code Coverage Tool 175 | .axoCover/* 176 | !.axoCover/settings.json 177 | 178 | # Visual Studio code coverage results 179 | *.coverage 180 | *.coveragexml 181 | 182 | # NCrunch 183 | _NCrunch_* 184 | .*crunch*.local.xml 185 | nCrunchTemp_* 186 | 187 | # MightyMoose 188 | *.mm.* 189 | AutoTest.Net/ 190 | 191 | # Web workbench (sass) 192 | .sass-cache/ 193 | 194 | # Installshield output folder 195 | [Ee]xpress/ 196 | 197 | # DocProject is a documentation generator add-in 198 | DocProject/buildhelp/ 199 | DocProject/Help/*.HxT 200 | DocProject/Help/*.HxC 201 | DocProject/Help/*.hhc 202 | DocProject/Help/*.hhk 203 | DocProject/Help/*.hhp 204 | DocProject/Help/Html2 205 | DocProject/Help/html 206 | 207 | # Click-Once directory 208 | publish/ 209 | 210 | # Publish Web Output 211 | *.[Pp]ublish.xml 212 | *.azurePubxml 213 | # Note: Comment the next line if you want to checkin your web deploy settings, 214 | # but database connection strings (with potential passwords) will be unencrypted 215 | *.pubxml 216 | *.publishproj 217 | 218 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 219 | # checkin your Azure Web App publish settings, but sensitive information contained 220 | # in these scripts will be unencrypted 221 | PublishScripts/ 222 | 223 | # NuGet Packages 224 | *.nupkg 225 | # The packages folder can be ignored because of Package Restore 226 | **/[Pp]ackages/* 227 | # except build/, which is used as an MSBuild target. 228 | !**/[Pp]ackages/build/ 229 | # Uncomment if necessary however generally it will be regenerated when needed 230 | #!**/[Pp]ackages/repositories.config 231 | # NuGet v3's project.json files produces more ignorable files 232 | *.nuget.props 233 | *.nuget.targets 234 | 235 | # Microsoft Azure Build Output 236 | csx/ 237 | *.build.csdef 238 | 239 | # Microsoft Azure Emulator 240 | ecf/ 241 | rcf/ 242 | 243 | # Windows Store app package directories and files 244 | AppPackages/ 245 | BundleArtifacts/ 246 | Package.StoreAssociation.xml 247 | _pkginfo.txt 248 | *.appx 249 | 250 | # Visual Studio cache files 251 | # files ending in .cache can be ignored 252 | *.[Cc]ache 253 | # but keep track of directories ending in .cache 254 | !*.[Cc]ache/ 255 | 256 | # Others 257 | ClientBin/ 258 | ~$* 259 | *~ 260 | *.dbmdl 261 | *.dbproj.schemaview 262 | *.jfm 263 | *.pfx 264 | *.publishsettings 265 | orleans.codegen.cs 266 | 267 | # Since there are multiple workflows, uncomment next line to ignore bower_components 268 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 269 | #bower_components/ 270 | 271 | # RIA/Silverlight projects 272 | Generated_Code/ 273 | 274 | # Backup & report files from converting an old project file 275 | # to a newer Visual Studio version. Backup files are not needed, 276 | # because we have git ;-) 277 | _UpgradeReport_Files/ 278 | Backup*/ 279 | UpgradeLog*.XML 280 | UpgradeLog*.htm 281 | 282 | # SQL Server files 283 | *.mdf 284 | *.ldf 285 | *.ndf 286 | 287 | # Business Intelligence projects 288 | *.rdl.data 289 | *.bim.layout 290 | *.bim_*.settings 291 | 292 | # Microsoft Fakes 293 | FakesAssemblies/ 294 | 295 | # GhostDoc plugin setting file 296 | *.GhostDoc.xml 297 | 298 | # Node.js Tools for Visual Studio 299 | .ntvs_analysis.dat 300 | node_modules/ 301 | 302 | # Typescript v1 declaration files 303 | typings/ 304 | 305 | # Visual Studio 6 build log 306 | *.plg 307 | 308 | # Visual Studio 6 workspace options file 309 | *.opt 310 | 311 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 312 | *.vbw 313 | 314 | # Visual Studio LightSwitch build output 315 | **/*.HTMLClient/GeneratedArtifacts 316 | **/*.DesktopClient/GeneratedArtifacts 317 | **/*.DesktopClient/ModelManifest.xml 318 | **/*.Server/GeneratedArtifacts 319 | **/*.Server/ModelManifest.xml 320 | _Pvt_Extensions 321 | 322 | # Paket dependency manager 323 | .paket/paket.exe 324 | paket-files/ 325 | 326 | # FAKE - F# Make 327 | .fake/ 328 | 329 | # JetBrains Rider 330 | .idea/ 331 | *.sln.iml 332 | 333 | # IDE - VSCode 334 | .vscode/* 335 | !.vscode/settings.json 336 | !.vscode/tasks.json 337 | !.vscode/launch.json 338 | !.vscode/extensions.json 339 | 340 | # CodeRush 341 | .cr/ 342 | 343 | # Python Tools for Visual Studio (PTVS) 344 | __pycache__/ 345 | *.pyc 346 | 347 | # Cake - Uncomment if you are using it 348 | # tools/** 349 | # !tools/packages.config 350 | 351 | # Tabs Studio 352 | *.tss 353 | 354 | # Telerik's JustMock configuration file 355 | *.jmconfig 356 | 357 | # BizTalk build output 358 | *.btp.cs 359 | *.btm.cs 360 | *.odx.cs 361 | *.xsd.cs 362 | 363 | # OpenCover UI analysis results 364 | OpenCover/ 365 | coverage/ 366 | 367 | ### macOS template 368 | # General 369 | .DS_Store 370 | .AppleDouble 371 | .LSOverride 372 | 373 | # Icon must end with two \r 374 | Icon 375 | 376 | # Thumbnails 377 | ._* 378 | 379 | # Files that might appear in the root of a volume 380 | .DocumentRevisions-V100 381 | .fseventsd 382 | .Spotlight-V100 383 | .TemporaryItems 384 | .Trashes 385 | .VolumeIcon.icns 386 | .com.apple.timemachine.donotpresent 387 | 388 | # Directories potentially created on remote AFP share 389 | .AppleDB 390 | .AppleDesktop 391 | Network Trash Folder 392 | Temporary Items 393 | .apdisk 394 | 395 | ======= 396 | # Local 397 | .env 398 | dist 399 | -------------------------------------------------------------------------------- /lib/services/listeners-explorer.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; 2 | import { ModuleRef, ModulesContainer } from '@nestjs/core'; 3 | import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; 4 | import { MetadataScanner } from '@nestjs/core/metadata-scanner'; 5 | import { Module } from '@nestjs/core/injector/module'; 6 | import { ParamMetadata } from '@nestjs/core/helpers/interfaces'; 7 | import { ExternalContextCreator } from '@nestjs/core/helpers/external-context-creator'; 8 | import { VK, Context, Updates, MessageContext, Composer } from 'vk-io'; 9 | import { AllowArray } from 'vk-io/lib/types'; 10 | import { NextMiddleware } from 'middleware-io'; 11 | import { SessionManager } from '@vk-io/session'; 12 | import { SceneManager, StepScene } from '@vk-io/scenes'; 13 | import { HearManager } from '@vk-io/hear'; 14 | 15 | import { MetadataAccessorService } from './metadata-accessor.service'; 16 | import { 17 | PARAM_ARGS_METADATA, 18 | VK_API_NAME, 19 | VK_MODULE_OPTIONS, 20 | VK_SESSION_MANAGER, 21 | VK_SCENE_MANAGER, 22 | VK_HEAR_MANAGER, 23 | VK_MANAGERS_OPTIONS, 24 | } from '../vk.constants'; 25 | import { BaseExplorerService } from './base-explorer.service'; 26 | import { VkParamsFactory } from '../factories/vk-params-factory'; 27 | import { VkontextType } from '../execution-context'; 28 | import { VkManagersOptions, VkModuleOptions } from '../interfaces'; 29 | 30 | @Injectable() 31 | export class ListenersExplorerService extends BaseExplorerService implements OnModuleInit { 32 | private readonly vkParamsFactory = new VkParamsFactory(); 33 | private vk: VK; 34 | 35 | constructor( 36 | @Inject(VK_HEAR_MANAGER) 37 | private readonly hearManagerProvider: HearManager, 38 | @Inject(VK_SESSION_MANAGER) 39 | private readonly sessionManagerProvider: SessionManager, 40 | @Inject(VK_SCENE_MANAGER) 41 | private readonly sceneManager: SceneManager, 42 | @Inject(VK_MODULE_OPTIONS) 43 | private readonly vkOptions: VkModuleOptions, 44 | @Inject(VK_MANAGERS_OPTIONS) 45 | private readonly vkManagersOptions: VkManagersOptions, 46 | @Inject(VK_API_NAME) 47 | private readonly vkName: string, 48 | 49 | private readonly moduleRef: ModuleRef, 50 | private readonly metadataAccessor: MetadataAccessorService, 51 | private readonly metadataScanner: MetadataScanner, 52 | private readonly modulesContainer: ModulesContainer, 53 | private readonly externalContextCreator: ExternalContextCreator, 54 | ) { 55 | super(); 56 | } 57 | 58 | onModuleInit(): void { 59 | this.vk = this.moduleRef.get(this.vkName, { strict: false }); 60 | 61 | if (this.vkOptions.middlewaresBefore) { 62 | const composer = Composer.builder(); 63 | for (const middleware of this.vkOptions.middlewaresBefore) { 64 | composer.use(middleware); 65 | } 66 | this.vk.updates.use(composer.compose()); 67 | } 68 | 69 | if (this.vkManagersOptions.useSessionManager !== false) { 70 | this.vk.updates.use(this.sessionManagerProvider.middleware); 71 | } 72 | 73 | if (this.vkManagersOptions.useSceneManager !== false) { 74 | this.vk.updates.use(this.sceneManager.middleware); 75 | this.vk.updates.use(this.sceneManager.middlewareIntercept); 76 | } 77 | 78 | this.explore(); 79 | 80 | if (this.vkManagersOptions.useHearManager !== false) { 81 | this.vk.updates.use(this.hearManagerProvider.middleware); 82 | } 83 | 84 | if (this.vkOptions.middlewaresAfter) { 85 | const composer = Composer.builder(); 86 | for (const middleware of this.vkOptions.middlewaresAfter) { 87 | composer.use(middleware); 88 | } 89 | this.vk.updates.use(composer.compose()); 90 | } 91 | } 92 | 93 | explore(): void { 94 | const modules = this.getModules(this.modulesContainer, this.vkOptions.include || []); 95 | 96 | this.registerUpdates(modules); 97 | this.registerScenes(modules); 98 | } 99 | 100 | private registerUpdates(modules: Module[]): void { 101 | const updates = this.flatMap(modules, (instance) => this.filterUpdates(instance)); 102 | updates.forEach((wrapper) => this.registerListeners(this.vk.updates, wrapper)); 103 | } 104 | 105 | private filterUpdates(wrapper: InstanceWrapper): InstanceWrapper { 106 | const { instance } = wrapper; 107 | if (!instance) return undefined; 108 | 109 | const isUpdate = this.metadataAccessor.isUpdate(wrapper.metatype); 110 | if (!isUpdate) return undefined; 111 | 112 | return wrapper; 113 | } 114 | 115 | private registerScenes(modules: Module[]): void { 116 | const scenes = this.flatMap(modules, (wrapper) => this.filterScenes(wrapper)); 117 | scenes.forEach((wrapper) => { 118 | const sceneId = this.metadataAccessor.getSceneMetadata(wrapper.instance.constructor); 119 | 120 | this.registerSceneSteps(sceneId, wrapper); 121 | }); 122 | } 123 | 124 | private filterScenes(wrapper: InstanceWrapper): InstanceWrapper { 125 | const { instance } = wrapper; 126 | if (!instance) return undefined; 127 | 128 | const isScene = this.metadataAccessor.isScene(wrapper.metatype); 129 | if (!isScene) return undefined; 130 | 131 | return wrapper; 132 | } 133 | 134 | private registerListeners(updates: Updates, wrapper: InstanceWrapper): void { 135 | const { instance } = wrapper; 136 | const prototype = Object.getPrototypeOf(instance); 137 | this.metadataScanner.scanFromPrototype(instance, prototype, (name) => 138 | this.registerIfListener(updates, instance, prototype, name), 139 | ); 140 | } 141 | 142 | private registerSceneSteps(sceneId: string, wrapper: InstanceWrapper): void { 143 | const { instance } = wrapper; 144 | const prototype = Object.getPrototypeOf(instance); 145 | 146 | const steps: { step: number; methodName: string }[] = []; 147 | 148 | let enterHandler; 149 | let leaveHandler; 150 | 151 | let index = 0; 152 | this.metadataScanner.scanFromPrototype(instance, prototype, (methodName) => { 153 | const methodRef = prototype[methodName]; 154 | const action = this.metadataAccessor.getSceneActionMetadata(methodRef); 155 | if (action) { 156 | if (action === 'enter') { 157 | enterHandler = this.createContextCallback(instance, prototype, methodName); 158 | } else { 159 | leaveHandler = this.createContextCallback(instance, prototype, methodName); 160 | } 161 | return; 162 | } 163 | const step = this.metadataAccessor.getSceneStepMetadata(methodRef); 164 | steps.push({ step: step ?? index++, methodName }); 165 | }); 166 | 167 | const scene = new StepScene(sceneId, { 168 | enterHandler, 169 | leaveHandler, 170 | steps: steps 171 | .sort((a, b) => a.step - b.step) 172 | .map((e) => { 173 | const listenerCallbackFn = this.createContextCallback(instance, prototype, e.methodName); 174 | return listenerCallbackFn; 175 | }), 176 | }); 177 | this.sceneManager.addScenes([scene]); 178 | } 179 | 180 | private registerIfListener(updates: Updates, instance: any, prototype: any, methodName: string): void { 181 | const methodRef = prototype[methodName]; 182 | const metadata = this.metadataAccessor.getListenerMetadata(methodRef); 183 | if (!metadata || metadata.length < 1) { 184 | return undefined; 185 | } 186 | 187 | const listenerCallbackFn = this.createContextCallback(instance, prototype, methodName); 188 | 189 | for (const { handlerType, method, event, args } of metadata) { 190 | const getHandler = 191 | () => 192 | async (ctx: Context, next: NextMiddleware): Promise => { 193 | const result = await listenerCallbackFn(ctx, next); 194 | if (result) { 195 | switch (true) { 196 | case ctx.is(['message']): { 197 | if (typeof result === 'string' || typeof result === 'object') { 198 | if (this.vkOptions.notReplyMessage) { 199 | await ctx.send(result); 200 | } else { 201 | await ctx.reply(result); 202 | } 203 | } 204 | break; 205 | } 206 | } 207 | } 208 | // TODO-Possible-Feature: Add more supported return types 209 | }; 210 | 211 | switch (handlerType) { 212 | case 'vk_updates': { 213 | if (method === 'use') { 214 | updates.use(getHandler()); 215 | } else { 216 | updates[method](event, [...args, getHandler()] as AllowArray); 217 | } 218 | break; 219 | } 220 | case 'hears': { 221 | // if (this.vkOptions.useHearManager === false) { 222 | // break; 223 | // } 224 | 225 | if (method === 'onFallback') { 226 | this.hearManagerProvider.onFallback(getHandler()); 227 | } else { 228 | // @ts-ignore 229 | this.hearManagerProvider.hear(event, ...[...args, getHandler()]); 230 | } 231 | break; 232 | } 233 | // TODO: remake it (support hearManager, etc) 234 | } 235 | } 236 | } 237 | 238 | createContextCallback>(instance: T, prototype: unknown, methodName: string) { 239 | const paramsFactory = this.vkParamsFactory; 240 | const resolverCallback = this.externalContextCreator.create, VkontextType>( 241 | instance, 242 | prototype[methodName], 243 | methodName, 244 | PARAM_ARGS_METADATA, 245 | paramsFactory, 246 | undefined, 247 | undefined, 248 | undefined, 249 | 'vk-io', 250 | ); 251 | return resolverCallback; 252 | } 253 | } 254 | --------------------------------------------------------------------------------