nestjs-telegraf/lib/telegraf-bot.service.ts

157 lines
4.1 KiB
TypeScript
Raw Normal View History

2020-01-12 01:58:34 +03:00
import { Injectable, Inject, Logger } from '@nestjs/common'
2019-02-28 11:29:26 +03:00
import { ModuleRef } from '@nestjs/core'
import Telegraf, { ContextMessageUpdate } from 'telegraf'
import { flatten, head } from 'lodash'
2020-01-12 02:41:27 +03:00
import { TelegramCatch, TelegramActionHandler } from './decorators'
import { TokenInjectionToken } from './telegraf.constants'
import {
TelegrafOptionsFactory,
TelegramErrorHandler,
Handler,
ContextTransformer,
} from './interfaces'
import { InvalidConfigurationException } from './exeptions'
2019-02-28 11:29:26 +03:00
@Injectable()
2020-01-12 02:41:27 +03:00
export class TelegrafBotService {
private readonly logger = new Logger(TelegrafBotService.name, true)
2019-03-03 15:43:30 +03:00
private readonly sitePublicUrl?: string
2020-01-12 02:41:27 +03:00
private readonly bot: Telegraf<ContextMessageUpdate>
2019-02-28 11:29:26 +03:00
private ref: ModuleRef
public constructor(
2020-01-12 02:41:27 +03:00
@Inject(TokenInjectionToken) options: TelegrafOptionsFactory,
2019-02-28 11:29:26 +03:00
) {
2020-01-12 02:41:27 +03:00
const { token, sitePublicUrl } = options.createTelegrafOptions()
2019-03-03 15:43:30 +03:00
this.sitePublicUrl = sitePublicUrl
2019-03-03 19:37:18 +03:00
this.bot = new Telegraf(token)
2019-02-28 11:29:26 +03:00
}
2019-07-16 18:56:02 +03:00
public init(ref: ModuleRef, devMode: boolean = false) {
2019-02-28 11:29:26 +03:00
this.ref = ref
const handlers = this.createHandlers()
2019-03-03 19:37:18 +03:00
this.setupOnStart(handlers)
2019-05-20 07:56:11 +03:00
this.setupOnMessage(handlers)
2019-03-03 19:37:18 +03:00
this.setupOnCommand(handlers)
2019-07-16 18:56:02 +03:00
if (devMode) {
this.startPolling()
}
2019-02-28 11:29:26 +03:00
}
2019-03-03 15:43:30 +03:00
public getMiddleware(path: string) {
if (!this.sitePublicUrl) {
throw new InvalidConfigurationException(
'sitePublicUrl',
'does not exist, but webook used',
)
}
const url = `${this.sitePublicUrl}/${path}`
2019-03-03 15:43:30 +03:00
this.bot.telegram
.setWebhook(url)
2020-01-12 02:41:27 +03:00
.then(() => this.logger.log(`Webhook set success @ ${url}`))
2019-03-03 15:43:30 +03:00
2019-03-03 20:00:03 +03:00
return this.bot.webhookCallback(`/${path}`)
2019-03-03 15:43:30 +03:00
}
public startPolling() {
this.bot.telegram.deleteWebhook().then(
() => this.bot.startPolling(),
() => {
// okay, never mind
},
)
2019-02-28 11:29:26 +03:00
}
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,
}),
)
},
),
)
}
2019-03-03 19:37:18 +03:00
private setupOnStart(handlers: Handler[]): void {
2019-02-28 11:29:26 +03:00
const onStart = handlers.filter(({ config }) => config.onStart)
if (onStart.length !== 1) {
throw new Error()
}
2019-03-03 19:37:18 +03:00
this.bot.start(this.adoptHandle(head(onStart)))
2019-02-28 11:29:26 +03:00
}
2019-05-20 07:56:11 +03:00
private setupOnMessage(handlers: Handler[]): void {
const onMessageHandlers = handlers.filter(({ config }) => config.message)
onMessageHandlers.forEach(handler => {
this.bot.hears(handler.config.message, this.adoptHandle(handler))
})
}
2019-03-03 19:37:18 +03:00
private setupOnCommand(handlers: Handler[]): void {
2019-02-28 11:29:26 +03:00
const commandHandlers = handlers.filter(({ config }) => config.command)
commandHandlers.forEach(handler => {
2019-03-03 19:37:18 +03:00
this.bot.command(handler.config.command, this.adoptHandle(handler))
2019-02-28 11:29:26 +03:00
})
}
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
}
}
}