mirror of
https://github.com/Maks1mS/nestjs-telegraf.git
synced 2024-12-24 15:04:38 +03:00
feat: add initial code
This commit is contained in:
commit
4324f0f1cf
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
6
.travis.yml
Normal file
6
.travis.yml
Normal file
@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '8'
|
||||
- '10'
|
||||
cache: yarn
|
||||
script: yarn ci
|
4
lib/Bot.ts
Normal file
4
lib/Bot.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import Telegraf from 'telegraf'
|
||||
import { Context } from './Context'
|
||||
|
||||
export type Bot = Telegraf<Context>
|
3
lib/Context.ts
Normal file
3
lib/Context.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { ContextMessageUpdate } from 'telegraf'
|
||||
|
||||
export type Context = ContextMessageUpdate
|
5
lib/ContextTransformer.ts
Normal file
5
lib/ContextTransformer.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { ContextMessageUpdate } from 'telegraf'
|
||||
|
||||
export interface ContextTransformer<T = any> {
|
||||
transform: (ctx: ContextMessageUpdate) => Promise<T>
|
||||
}
|
13
lib/HandleParameters.ts
Normal file
13
lib/HandleParameters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ContextTransformer } from './ContextTransformer'
|
||||
import { Type } from '@nestjs/common'
|
||||
|
||||
interface ArgumentTransformation {
|
||||
index: number
|
||||
transform: Type<ContextTransformer>
|
||||
}
|
||||
|
||||
export interface HandleParameters {
|
||||
onStart?: boolean
|
||||
command?: string
|
||||
transformations?: ArgumentTransformation[]
|
||||
}
|
6
lib/Handler.ts
Normal file
6
lib/Handler.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { HandleParameters } from './HandleParameters'
|
||||
|
||||
export interface Handler {
|
||||
handle: (...args: any[]) => Promise<void>
|
||||
config: HandleParameters
|
||||
}
|
121
lib/TelegramBot.ts
Normal file
121
lib/TelegramBot.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { Injectable, Inject } from '@nestjs/common'
|
||||
import { ModuleRef } from '@nestjs/core'
|
||||
import Telegraf, { ContextMessageUpdate } from 'telegraf'
|
||||
import { flatten, head } from 'lodash'
|
||||
|
||||
import { ContextTransformer } from './ContextTransformer'
|
||||
import { TelegramCatch } from './decorators/TelegramCatch'
|
||||
import { TelegramErrorHandler } from './interfaces/TelegramErrorHandler'
|
||||
import { Handler } from './Handler'
|
||||
import { Bot } from './Bot'
|
||||
import { TelegramActionHandler } from './decorators/TelegramActionHandler'
|
||||
import { TokenInjectionToken } from './TokenInjectionToken'
|
||||
import { TelegramModuleOptionsFactory } from 'TelegramModuleOptionsFactory'
|
||||
|
||||
@Injectable()
|
||||
export class TelegramBot {
|
||||
private readonly token: string
|
||||
private bot: Bot
|
||||
private ref: ModuleRef
|
||||
|
||||
public constructor(
|
||||
@Inject(TokenInjectionToken) factory: TelegramModuleOptionsFactory,
|
||||
) {
|
||||
this.token = factory.createOptions().token
|
||||
}
|
||||
|
||||
public init(ref: ModuleRef) {
|
||||
this.ref = ref
|
||||
|
||||
const bot = new Telegraf(this.token)
|
||||
|
||||
const handlers = this.createHandlers()
|
||||
|
||||
this.setupOnStart(bot, handlers)
|
||||
this.setupOnCommand(bot, handlers)
|
||||
|
||||
this.bot = bot
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.bot.startPolling()
|
||||
}
|
||||
|
||||
private createHandlers(): Handler[] {
|
||||
return flatten(
|
||||
Array.from((TelegramActionHandler.handlers || new Map()).entries()).map(
|
||||
([handlerClass, classConfig]) => {
|
||||
const handlerInstance = this.ref.get(handlerClass, { strict: false })
|
||||
|
||||
return Array.from(classConfig.entries()).map(
|
||||
([methodName, methodCondig]) => ({
|
||||
handle: handlerInstance[methodName].bind(handlerInstance),
|
||||
config: methodCondig,
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private setupOnStart(bot: Bot, handlers: Handler[]): void {
|
||||
const onStart = handlers.filter(({ config }) => config.onStart)
|
||||
|
||||
if (onStart.length !== 1) {
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
bot.start(this.adoptHandle(head(onStart)))
|
||||
}
|
||||
|
||||
private setupOnCommand(bot: Bot, handlers: Handler[]): void {
|
||||
const commandHandlers = handlers.filter(({ config }) => config.command)
|
||||
|
||||
commandHandlers.forEach(handler => {
|
||||
bot.command(handler.config.command, this.adoptHandle(handler))
|
||||
})
|
||||
}
|
||||
|
||||
private adoptHandle({ handle, config }: Handler) {
|
||||
const errorHandler = this.createCatch()
|
||||
|
||||
return async (ctx: ContextMessageUpdate) => {
|
||||
const args = await Promise.all(
|
||||
(config.transformations || [])
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map(({ transform }) =>
|
||||
this.ref
|
||||
.get<ContextTransformer>(transform, { strict: false })
|
||||
.transform(ctx),
|
||||
),
|
||||
)
|
||||
|
||||
return handle(ctx, ...args).catch(errorHandler(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
private createCatch() {
|
||||
const handlers = Array.from(
|
||||
(TelegramCatch.handlers || new Map()).entries(),
|
||||
).map(([errorType, handlerType]) => {
|
||||
const handler = this.ref.get<TelegramErrorHandler>(handlerType, {
|
||||
strict: false,
|
||||
})
|
||||
|
||||
return {
|
||||
errorType,
|
||||
handler,
|
||||
}
|
||||
})
|
||||
|
||||
return (ctx: ContextMessageUpdate) => (e: any) => {
|
||||
for (const { errorType, handler } of handlers) {
|
||||
if (e instanceof (errorType as any)) {
|
||||
return handler.catch(ctx, e)
|
||||
}
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
3
lib/TelegramModuleOptions.ts
Normal file
3
lib/TelegramModuleOptions.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface TelegramModuleOptions {
|
||||
token: string
|
||||
}
|
5
lib/TelegramModuleOptionsFactory.ts
Normal file
5
lib/TelegramModuleOptionsFactory.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { TelegramModuleOptions } from './TelegramModuleOptions'
|
||||
|
||||
export interface TelegramModuleOptionsFactory {
|
||||
createOptions(): TelegramModuleOptions
|
||||
}
|
1
lib/TokenInjectionToken.ts
Normal file
1
lib/TokenInjectionToken.ts
Normal file
@ -0,0 +1 @@
|
||||
export const TokenInjectionToken = Symbol('TokenInjectionToken')
|
18
lib/decorators/PipeContext.ts
Normal file
18
lib/decorators/PipeContext.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Type } from '@nestjs/common'
|
||||
import { ContextTransformer } from '../ContextTransformer'
|
||||
import { addHandlerToStore } from './TelegramActionHandler'
|
||||
|
||||
export const PipeContext = <T>(transform: Type<ContextTransformer<T>>) => (
|
||||
target: Object,
|
||||
propertyKey: string,
|
||||
parameterIndex: number,
|
||||
) => {
|
||||
addHandlerToStore(target, propertyKey, {
|
||||
transformations: [
|
||||
{
|
||||
index: parameterIndex,
|
||||
transform,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
44
lib/decorators/TelegramActionHandler.ts
Normal file
44
lib/decorators/TelegramActionHandler.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { HandleParameters } from '../HandleParameters'
|
||||
|
||||
type Decorator = (
|
||||
params: HandleParameters,
|
||||
) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void
|
||||
|
||||
type HandlerDecorator = Decorator & {
|
||||
handlers?: Map<any, Map<string, HandleParameters>>
|
||||
}
|
||||
|
||||
export const TelegramActionHandler: HandlerDecorator = (
|
||||
parameters: HandleParameters,
|
||||
) => (target: any, propertyKey: string) => {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
addHandlerToStore(target, propertyKey, parameters)
|
||||
}
|
||||
|
||||
export const addHandlerToStore = (
|
||||
instance: Object,
|
||||
name: string,
|
||||
config: HandleParameters,
|
||||
) => {
|
||||
const handlerClass = instance.constructor
|
||||
|
||||
if (!TelegramActionHandler.handlers) {
|
||||
TelegramActionHandler.handlers = new Map()
|
||||
}
|
||||
|
||||
if (!TelegramActionHandler.handlers.get(handlerClass)) {
|
||||
TelegramActionHandler.handlers.set(handlerClass, new Map())
|
||||
}
|
||||
|
||||
const oldParameters =
|
||||
TelegramActionHandler.handlers.get(handlerClass).get(name) || {}
|
||||
|
||||
TelegramActionHandler.handlers.get(handlerClass).set(name, {
|
||||
...oldParameters,
|
||||
...config,
|
||||
transformations: [
|
||||
...(oldParameters.transformations || []),
|
||||
...(config.transformations || []),
|
||||
],
|
||||
})
|
||||
}
|
18
lib/decorators/TelegramCatch.ts
Normal file
18
lib/decorators/TelegramCatch.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Type } from '@nestjs/common'
|
||||
import { TelegramErrorHandler } from '../interfaces/TelegramErrorHandler'
|
||||
|
||||
type Decorator = (error: any) => ClassDecorator
|
||||
|
||||
type HandlerDecorator = Decorator & {
|
||||
handlers?: Map<Error, Type<TelegramErrorHandler>>
|
||||
}
|
||||
|
||||
export const TelegramCatch: HandlerDecorator = error => target => {
|
||||
if (!TelegramCatch.handlers) {
|
||||
TelegramCatch.handlers = new Map()
|
||||
}
|
||||
|
||||
TelegramCatch.handlers.set(error, target as any)
|
||||
|
||||
return target
|
||||
}
|
14
lib/index.ts
Normal file
14
lib/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export { TelegramModule } from './telegram.module'
|
||||
export { TelegramBot } from './TelegramBot'
|
||||
|
||||
export { TelegramModuleOptionsFactory } from './TelegramModuleOptionsFactory'
|
||||
export { TelegramModuleOptions } from './TelegramModuleOptions'
|
||||
|
||||
export { PipeContext } from './decorators/PipeContext'
|
||||
export { TelegramActionHandler } from './decorators/TelegramActionHandler'
|
||||
export { TelegramCatch } from './decorators/TelegramCatch'
|
||||
|
||||
export { TelegramErrorHandler } from './interfaces/TelegramErrorHandler'
|
||||
|
||||
export { ContextTransformer } from './ContextTransformer'
|
||||
export { Context } from './Context'
|
5
lib/interfaces/TelegramErrorHandler.ts
Normal file
5
lib/interfaces/TelegramErrorHandler.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { ContextMessageUpdate } from 'telegraf'
|
||||
|
||||
export interface TelegramErrorHandler<E = any> {
|
||||
catch(ctx: ContextMessageUpdate, error: E): Promise<void>
|
||||
}
|
36
lib/telegram.module.ts
Normal file
36
lib/telegram.module.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
NestModule,
|
||||
DynamicModule,
|
||||
} from '@nestjs/common'
|
||||
import { ModuleMetadata, Type } from '@nestjs/common/interfaces'
|
||||
import { TelegramBot } from './TelegramBot'
|
||||
import { TelegramModuleOptionsFactory } from './TelegramModuleOptionsFactory'
|
||||
import { TokenInjectionToken } from './TokenInjectionToken'
|
||||
|
||||
interface TelegramFactory extends Pick<ModuleMetadata, 'imports'> {
|
||||
useClass?: Type<TelegramModuleOptionsFactory>
|
||||
inject?: any[]
|
||||
}
|
||||
|
||||
@Module({})
|
||||
export class TelegramModule implements NestModule {
|
||||
public configure(consumer: MiddlewareConsumer) {
|
||||
// pass
|
||||
}
|
||||
|
||||
static fromFactory(factory: TelegramFactory): DynamicModule {
|
||||
return {
|
||||
module: TelegramModule,
|
||||
providers: [
|
||||
TelegramBot,
|
||||
{
|
||||
provide: TokenInjectionToken,
|
||||
useClass: factory.useClass,
|
||||
},
|
||||
],
|
||||
exports: [TelegramBot],
|
||||
}
|
||||
}
|
||||
}
|
37
package.json
Normal file
37
package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "nest-telegram",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/index.js",
|
||||
"repository": "git@github.com:igorkamyshev/nest-telegram.git",
|
||||
"author": "Igor Kamyshev <igor@kamyshev.me>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@solid-soda/scripts": "^1.2.4",
|
||||
"@team-griffin/install-self-peers": "^1.1.1",
|
||||
"@types/lodash": "^4.14.121"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "yarn soda lint-staged",
|
||||
"commit-msg": "yarn soda commitlint"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf dist && tsc",
|
||||
"prepare": "install-self-peers -- --ignore-scripts && yarn build",
|
||||
"ci": "yarn soda lint",
|
||||
"s": "yarn soda"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^5.7.3",
|
||||
"@nestjs/core": "^5.7.3",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.11",
|
||||
"telegraf": "^3.27.1"
|
||||
}
|
||||
}
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2017"],
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./lib"
|
||||
},
|
||||
"include": ["lib/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user