diff --git a/.gitignore b/.gitignore index f9a2902..8a8a4fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# lock +package-lock.json +yarn.lock + # dependencies /node_modules diff --git a/index.js b/index.js index a82ea05..9f5e27a 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ "use strict"; function __export(m) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } exports.__esModule = true; -__export(require("./dist")); \ No newline at end of file +__export(require("./dist")); diff --git a/lib/decorators/action.decorator.ts b/lib/decorators/action.decorator.ts deleted file mode 100644 index a6c7a99..0000000 --- a/lib/decorators/action.decorator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; -import { HearsTriggers } from 'telegraf/typings/composer'; -import { Context } from '../interfaces'; - -export type TelegrafActionTriggers = HearsTriggers; - -export interface ActionOptions { - triggers: TelegrafActionTriggers; -} - -/** - * Registers middleware for handling callback_data actions with regular expressions. - * - * @see https://telegraf.js.org/#/?id=action - */ -export const Action = (triggers: TelegrafActionTriggers): MethodDecorator => { - return SetMetadata(DECORATORS.ACTION, { triggers }); -}; - -/** - * Registers middleware for handling callback_data actions with regular expressions. - * - * @see https://telegraf.js.org/#/?id=action - * @deprecated since v2, use Action decorator instead. - */ -export const TelegrafAction = Action; diff --git a/lib/decorators/cashtag.decorator.ts b/lib/decorators/cashtag.decorator.ts deleted file mode 100644 index e85efb3..0000000 --- a/lib/decorators/cashtag.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -export type TelegrafCashtagCashtag = string | string[]; - -export interface CashtagOptions { - cashtag: TelegrafCashtagCashtag; -} - -/** - * Cashtag handling. - * - * @see https://telegraf.js.org/#/?id=cashtag - */ -export const Cashtag = (cashtag: TelegrafCashtagCashtag): MethodDecorator => { - return SetMetadata(DECORATORS.CASHTAG, { cashtag }); -}; - -/** - * Cashtag handling. - * - * @see https://telegraf.js.org/#/?id=cashtag - * @deprecated since v2, use Cashtag decorator instead. - */ -export const TelegrafCashtag = Cashtag; diff --git a/lib/decorators/command.decorator.ts b/lib/decorators/command.decorator.ts deleted file mode 100644 index 6100612..0000000 --- a/lib/decorators/command.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -export type TelegrafCommandCommands = string | string[]; - -export interface CommandOptions { - commands: TelegrafCommandCommands; -} - -/** - * Command handling. - * - * @see https://telegraf.js.org/#/?id=command - */ -export const Command = (commands: TelegrafCommandCommands): MethodDecorator => { - return SetMetadata(DECORATORS.COMMAND, { commands }); -}; - -/** - * Command handling. - * - * @see https://telegraf.js.org/#/?id=command - * @deprecated since v2, use Command decorator instead. - */ -export const TelegrafCommand = Command; diff --git a/lib/decorators/core/index.ts b/lib/decorators/core/index.ts new file mode 100644 index 0000000..baddc8e --- /dev/null +++ b/lib/decorators/core/index.ts @@ -0,0 +1,3 @@ +export * from './update.decorator'; +export * from './scene.decorator'; +export * from './inject-bot.decorator'; diff --git a/lib/decorators/core/inject-bot.decorator.ts b/lib/decorators/core/inject-bot.decorator.ts new file mode 100644 index 0000000..73299e8 --- /dev/null +++ b/lib/decorators/core/inject-bot.decorator.ts @@ -0,0 +1,5 @@ +import { Inject } from '@nestjs/common'; +import { getBotToken } from '../../utils'; + +export const InjectBot = (name?: string): ParameterDecorator => + Inject(getBotToken(name)); diff --git a/lib/decorators/core/scene.decorator.ts b/lib/decorators/core/scene.decorator.ts new file mode 100644 index 0000000..391c842 --- /dev/null +++ b/lib/decorators/core/scene.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; +import { SCENE_METADATA } from '../../telegraf.constants'; + +/** + * TODO + */ +export const Scene = (id: string): ClassDecorator => + SetMetadata(SCENE_METADATA, id); diff --git a/lib/decorators/update.decorator.ts b/lib/decorators/core/update.decorator.ts similarity index 53% rename from lib/decorators/update.decorator.ts rename to lib/decorators/core/update.decorator.ts index 1946199..2002fec 100644 --- a/lib/decorators/update.decorator.ts +++ b/lib/decorators/core/update.decorator.ts @@ -1,8 +1,8 @@ import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; +import { UPDATE_METADATA } from '../../telegraf.constants'; /** * `@Update` decorator, it's like NestJS `@Controller` decorator, * but for Telegram Bot API updates. */ -export const Update = (): ClassDecorator => SetMetadata(DECORATORS.UPDATE, {}); +export const Update = (): ClassDecorator => SetMetadata(UPDATE_METADATA, true); diff --git a/lib/decorators/entity.decorator.ts b/lib/decorators/entity.decorator.ts deleted file mode 100644 index 53be71f..0000000 --- a/lib/decorators/entity.decorator.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -export type TelegrafEntityEntity = - | string - | string[] - | RegExp - | RegExp[] - | Function; - -export interface EntityOptions { - entity: TelegrafEntityEntity; -} - -/** - * Entity handling. - * - * @see https://telegraf.js.org/#/?id=entity - */ -export const Entity = (entity: TelegrafEntityEntity): MethodDecorator => { - return SetMetadata(DECORATORS.ENTITY, { entity }); -}; - -/** - * Entity handling. - * - * @see https://telegraf.js.org/#/?id=entity - * @deprecated since v2, use Entity decorator instead. - */ -export const TelegrafEntity = Entity; diff --git a/lib/decorators/game-query.decorator.ts b/lib/decorators/game-query.decorator.ts deleted file mode 100644 index c9d240b..0000000 --- a/lib/decorators/game-query.decorator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -/** - * Registers middleware for handling callback_data actions with game query. - * - * @see https://telegraf.js.org/#/?id=inlinequery - */ -export const GameQuery = (): MethodDecorator => { - return SetMetadata(DECORATORS.GAME_QUERY, {}); -}; - -/** - * Registers middleware for handling callback_data actions with game query. - * - * @see https://telegraf.js.org/#/?id=inlinequery - * @deprecated since v2, use Action decorator instead. - */ -export const TelegrafGameQuery = GameQuery; diff --git a/lib/decorators/hashtag.decorator.ts b/lib/decorators/hashtag.decorator.ts deleted file mode 100644 index 88e03cb..0000000 --- a/lib/decorators/hashtag.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -export type TelegrafHashtagHashtag = string | string[]; - -export interface HashtagOptions { - hashtag: TelegrafHashtagHashtag; -} - -/** - * Hashtag handling. - * - * @see https://telegraf.js.org/#/?id=hashtag - */ -export const Hashtag = (hashtag: TelegrafHashtagHashtag): MethodDecorator => { - return SetMetadata(DECORATORS.HASHTAG, { hashtag }); -}; - -/** - * Hashtag handling. - * - * @see https://telegraf.js.org/#/?id=hashtag - * @deprecated since v2, use Hashtag decorator instead. - */ -export const TelegrafHashtag = Hashtag; diff --git a/lib/decorators/hears.decorator.ts b/lib/decorators/hears.decorator.ts deleted file mode 100644 index ecb7c56..0000000 --- a/lib/decorators/hears.decorator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; -import { HearsTriggers } from 'telegraf/typings/composer'; -import { Context } from '../interfaces'; - -export type TelegrafHearsTriggers = HearsTriggers; - -export interface HearsOptions { - triggers: TelegrafHearsTriggers; -} - -/** - * Registers middleware for handling text messages. - * - * @see https://telegraf.js.org/#/?id=hears - */ -export const Hears = (triggers: TelegrafHearsTriggers): MethodDecorator => { - return SetMetadata(DECORATORS.HEARS, { triggers: triggers }); -}; - -/** - * Registers middleware for handling text messages. - * - * @see https://telegraf.js.org/#/?id=hears - * @deprecated since v2, use Hears decorator instead. - */ -export const TelegrafHears = Hears; diff --git a/lib/decorators/help.decorator.ts b/lib/decorators/help.decorator.ts deleted file mode 100644 index ced12b1..0000000 --- a/lib/decorators/help.decorator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -/** - * Handler for /help command. - * - * @see https://telegraf.js.org/#/?id=help - */ -export const Help = (): MethodDecorator => { - return SetMetadata(DECORATORS.HELP, {}); -}; - -/** - * Handler for /help command. - * - * @see https://telegraf.js.org/#/?id=help - * @deprecated since v2, use Help decorator instead. - */ -export const TelegrafHelp = Help; diff --git a/lib/decorators/index.ts b/lib/decorators/index.ts index 03af4ba..31fa986 100644 --- a/lib/decorators/index.ts +++ b/lib/decorators/index.ts @@ -1,18 +1,3 @@ -export * from './action.decorator'; -export * from './cashtag.decorator'; -export * from './command.decorator'; -export * from './entity.decorator'; -export * from './game-query.decorator'; -export * from './hashtag.decorator'; -export * from './hears.decorator'; -export * from './help.decorator'; -export * from './inject-bot.decorator'; -export * from './inline-query.decorator'; -export * from './mention.decorator'; -export * from './on.decorator'; -export * from './phone.decorator'; -export * from './settings.decorator'; -export * from './start.decorator'; -export * from './update.decorator'; -export * from './update-hooks.decorators'; -export * from './use.decorator'; +export * from './core'; +export * from './listeners'; +export * from './scene'; diff --git a/lib/decorators/inject-bot.decorator.ts b/lib/decorators/inject-bot.decorator.ts deleted file mode 100644 index 84f1c50..0000000 --- a/lib/decorators/inject-bot.decorator.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { TELEGRAF_PROVIDER } from '../telegraf.constants'; - -export const InjectBot = (): ParameterDecorator => Inject(TELEGRAF_PROVIDER); diff --git a/lib/decorators/inline-query.decorator.ts b/lib/decorators/inline-query.decorator.ts deleted file mode 100644 index 8f2b0fa..0000000 --- a/lib/decorators/inline-query.decorator.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; -import * as tt from 'telegraf/typings/telegram-types'; - -export type TelegrafInlineQueryTriggers = string | string[] | RegExp | RegExp[]; - -export interface InlineQueryOptions { - triggers?: TelegrafInlineQueryTriggers; - updateType: - | tt.UpdateType - | tt.UpdateType[] - | tt.MessageSubTypes - | tt.MessageSubTypes[]; -} - -/** - * Registers middleware for handling inline_query actions with regular expressions. - * - * @see https://telegraf.js.org/#/?id=inlinequery - */ -export const InlineQuery = ( - triggers?: TelegrafInlineQueryTriggers, -): MethodDecorator => { - return SetMetadata(DECORATORS.INLINE_QUERY, { - triggers, - updateType: 'inline_query', - }); -}; - -/** - * Registers middleware for handling inline_query actions with regular expressions. - * - * @see https://telegraf.js.org/#/?id=inlinequery - * @deprecated since v2, use InlineQuery decorator instead. - */ -export const TelegrafInlineQuery = InlineQuery; diff --git a/lib/decorators/listeners/action.decorator.ts b/lib/decorators/listeners/action.decorator.ts new file mode 100644 index 0000000..4b14901 --- /dev/null +++ b/lib/decorators/listeners/action.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling callback_data actions with regular expressions. + * + * @see https://telegraf.js.org/#/?id=action + */ +export const Action = createUpdateListenerDecorator('action'); diff --git a/lib/decorators/listeners/cashtag.decorator.ts b/lib/decorators/listeners/cashtag.decorator.ts new file mode 100644 index 0000000..c3e37e9 --- /dev/null +++ b/lib/decorators/listeners/cashtag.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Cashtag handling. + * + * @see https://telegraf.js.org/#/?id=cashtag + */ +export const Cashtag = createUpdateListenerDecorator('cashtag'); diff --git a/lib/decorators/listeners/command.decorator.ts b/lib/decorators/listeners/command.decorator.ts new file mode 100644 index 0000000..0e7806e --- /dev/null +++ b/lib/decorators/listeners/command.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Command handling. + * + * @see https://telegraf.js.org/#/?id=command + */ +export const Command = createUpdateListenerDecorator('command'); diff --git a/lib/decorators/listeners/email.decorator.ts b/lib/decorators/listeners/email.decorator.ts new file mode 100644 index 0000000..e9bb1a6 --- /dev/null +++ b/lib/decorators/listeners/email.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling messages with email entity. + * + * @see https://telegraf.js.org/#/?id=telegraf-email + */ +export const Email = createUpdateListenerDecorator('email'); diff --git a/lib/decorators/listeners/game-query.decorator.ts b/lib/decorators/listeners/game-query.decorator.ts new file mode 100644 index 0000000..347f998 --- /dev/null +++ b/lib/decorators/listeners/game-query.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling callback_data actions with game query. + * + * @see https://telegraf.js.org/#/?id=inlinequery + */ +export const GameQuery = createUpdateListenerDecorator('gameQuery'); diff --git a/lib/decorators/listeners/hashtag.decorator.ts b/lib/decorators/listeners/hashtag.decorator.ts new file mode 100644 index 0000000..86e9e13 --- /dev/null +++ b/lib/decorators/listeners/hashtag.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Hashtag handling. + * + * @see https://telegraf.js.org/#/?id=hashtag + */ +export const Hashtag = createUpdateListenerDecorator('hashtag'); diff --git a/lib/decorators/listeners/hears.decorator.ts b/lib/decorators/listeners/hears.decorator.ts new file mode 100644 index 0000000..79d71af --- /dev/null +++ b/lib/decorators/listeners/hears.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling text messages. + * + * @see https://telegraf.js.org/#/?id=hears + */ +export const Hears = createUpdateListenerDecorator('hears'); diff --git a/lib/decorators/listeners/help.decorator.ts b/lib/decorators/listeners/help.decorator.ts new file mode 100644 index 0000000..36ac9e4 --- /dev/null +++ b/lib/decorators/listeners/help.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Handler for /help command. + * + * @see https://telegraf.js.org/#/?id=help + */ +export const Help = createUpdateListenerDecorator('help'); diff --git a/lib/decorators/listeners/index.ts b/lib/decorators/listeners/index.ts new file mode 100644 index 0000000..a332f5a --- /dev/null +++ b/lib/decorators/listeners/index.ts @@ -0,0 +1,18 @@ +export * from './on.decorator'; +export * from './use.decorator'; +export * from './action.decorator'; +export * from './cashtag.decorator'; +export * from './command.decorator'; +export * from './game-query.decorator'; +export * from './hashtag.decorator'; +export * from './hears.decorator'; +export * from './help.decorator'; +export * from './inline-query.decorator'; +export * from './mention.decorator'; +export * from './phone.decorator'; +export * from './settings.decorator'; +export * from './start.decorator'; +export * from './email.decorator'; +export * from './url.decorator'; +export * from './text-link.decorator'; +export * from './text-mention.decorator'; diff --git a/lib/decorators/listeners/inline-query.decorator.ts b/lib/decorators/listeners/inline-query.decorator.ts new file mode 100644 index 0000000..722a348 --- /dev/null +++ b/lib/decorators/listeners/inline-query.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling inline_query actions with regular expressions. + * + * @see https://telegraf.js.org/#/?id=inlinequery + */ +export const InlineQuery = createUpdateListenerDecorator('inlineQuery'); diff --git a/lib/decorators/listeners/mention.decorator.ts b/lib/decorators/listeners/mention.decorator.ts new file mode 100644 index 0000000..e4341a0 --- /dev/null +++ b/lib/decorators/listeners/mention.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Mention handling. + * + * @see https://telegraf.js.org/#/?id=mention + */ +export const Mention = createUpdateListenerDecorator('mention'); diff --git a/lib/decorators/listeners/on.decorator.ts b/lib/decorators/listeners/on.decorator.ts new file mode 100644 index 0000000..9f3c0fd --- /dev/null +++ b/lib/decorators/listeners/on.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for provided update type. + * + * @see https://telegraf.js.org/#/?id=on + */ +export const On = createUpdateListenerDecorator('on'); diff --git a/lib/decorators/listeners/phone.decorator.ts b/lib/decorators/listeners/phone.decorator.ts new file mode 100644 index 0000000..0f68380 --- /dev/null +++ b/lib/decorators/listeners/phone.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Phone number handling. + * + * @see https://telegraf.js.org/#/?id=phone + */ +export const Phone = createUpdateListenerDecorator('phone'); diff --git a/lib/decorators/listeners/settings.decorator.ts b/lib/decorators/listeners/settings.decorator.ts new file mode 100644 index 0000000..6cca7c7 --- /dev/null +++ b/lib/decorators/listeners/settings.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Handler for /settings command. + * + * @see https://telegraf.js.org/#/?id=settings + */ +export const Settings = createUpdateListenerDecorator('settings'); diff --git a/lib/decorators/listeners/start.decorator.ts b/lib/decorators/listeners/start.decorator.ts new file mode 100644 index 0000000..48bdc5b --- /dev/null +++ b/lib/decorators/listeners/start.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Handler for /start command. + * + * @see https://telegraf.js.org/#/?id=start + */ +export const Start = createUpdateListenerDecorator('start'); diff --git a/lib/decorators/listeners/text-link.decorator.ts b/lib/decorators/listeners/text-link.decorator.ts new file mode 100644 index 0000000..c62439a --- /dev/null +++ b/lib/decorators/listeners/text-link.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling messages with text_link entity. + * + * @see https://telegraf.js.org/#/?id=telegraf-textlink + */ +export const TextLink = createUpdateListenerDecorator('textLink'); diff --git a/lib/decorators/listeners/text-mention.decorator.ts b/lib/decorators/listeners/text-mention.decorator.ts new file mode 100644 index 0000000..895a458 --- /dev/null +++ b/lib/decorators/listeners/text-mention.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling messages with text_mention entity. + * + * @see https://telegraf.js.org/#/?id=telegraf-textlink + */ +export const TextMention = createUpdateListenerDecorator('textMention'); diff --git a/lib/decorators/listeners/url.decorator.ts b/lib/decorators/listeners/url.decorator.ts new file mode 100644 index 0000000..843aa41 --- /dev/null +++ b/lib/decorators/listeners/url.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers middleware for handling messages with url entity. + * + * @see https://telegraf.js.org/#/?id=telegraf-url + */ +export const Url = createUpdateListenerDecorator('url'); diff --git a/lib/decorators/listeners/use.decorator.ts b/lib/decorators/listeners/use.decorator.ts new file mode 100644 index 0000000..ba83af5 --- /dev/null +++ b/lib/decorators/listeners/use.decorator.ts @@ -0,0 +1,8 @@ +import { createUpdateListenerDecorator } from '../../helpers'; + +/** + * Registers a middleware. + * + * @see https://telegraf.js.org/#/?id=use + */ +export const Use = createUpdateListenerDecorator('use'); diff --git a/lib/decorators/mention.decorator.ts b/lib/decorators/mention.decorator.ts deleted file mode 100644 index 7611c8e..0000000 --- a/lib/decorators/mention.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -export type TelegrafMentionUsername = string | string[]; - -export interface MentionOptions { - username: TelegrafMentionUsername; -} - -/** - * Mention handling. - * - * @see https://telegraf.js.org/#/?id=mention - */ -export const Mention = (username: TelegrafMentionUsername): MethodDecorator => { - return SetMetadata(DECORATORS.MENTION, { username }); -}; - -/** - * Mention handling. - * - * @see https://telegraf.js.org/#/?id=mention - * @deprecated since v2, use Mention decorator instead. - */ -export const TelegrafMention = Mention; diff --git a/lib/decorators/on.decorator.ts b/lib/decorators/on.decorator.ts deleted file mode 100644 index bc36c94..0000000 --- a/lib/decorators/on.decorator.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; -import { UpdateType, MessageSubTypes } from 'telegraf/typings/telegram-types'; - -export type TelegrafOnUpdateTypes = - | UpdateType - | UpdateType[] - | MessageSubTypes - | MessageSubTypes[]; - -export interface OnOptions { - updateTypes: TelegrafOnUpdateTypes; -} - -/** - * Registers middleware for provided update type. - * - * @see https://telegraf.js.org/#/?id=on - */ -export const On = (updateTypes: TelegrafOnUpdateTypes): MethodDecorator => { - return SetMetadata(DECORATORS.ON, { updateTypes: updateTypes }); -}; - -/** - * Registers middleware for provided update type. - * - * @see https://telegraf.js.org/#/?id=on - * @deprecated since v2, use On decorator instead. - */ -export const TelegrafOn = On; diff --git a/lib/decorators/phone.decorator.ts b/lib/decorators/phone.decorator.ts deleted file mode 100644 index 90e6342..0000000 --- a/lib/decorators/phone.decorator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -export type TelegrafPhonePhone = string | string[]; - -export interface PhoneOptions { - phone: TelegrafPhonePhone; -} - -/** - * Phone number handling. - * - * @see https://telegraf.js.org/#/?id=phone - */ -export const Phone = (phone: TelegrafPhonePhone): MethodDecorator => { - return SetMetadata(DECORATORS.PHONE, { phone }); -}; - -/** - * Phone number handling. - * - * @see https://telegraf.js.org/#/?id=phone - * @deprecated since v2, use Phone decorator instead. - */ -export const TelegrafPhone = Phone; diff --git a/lib/decorators/scene/index.ts b/lib/decorators/scene/index.ts new file mode 100644 index 0000000..b1d61b2 --- /dev/null +++ b/lib/decorators/scene/index.ts @@ -0,0 +1,2 @@ +export * from './scene-enter.decorator'; +export * from './scene-leave.decorator'; diff --git a/lib/decorators/scene/scene-enter.decorator.ts b/lib/decorators/scene/scene-enter.decorator.ts new file mode 100644 index 0000000..914b2d7 --- /dev/null +++ b/lib/decorators/scene/scene-enter.decorator.ts @@ -0,0 +1,3 @@ +import { createSceneListenerDecorator } from '../../helpers'; + +export const SceneEnter = createSceneListenerDecorator('enter'); diff --git a/lib/decorators/scene/scene-leave.decorator.ts b/lib/decorators/scene/scene-leave.decorator.ts new file mode 100644 index 0000000..19b970e --- /dev/null +++ b/lib/decorators/scene/scene-leave.decorator.ts @@ -0,0 +1,3 @@ +import { createSceneListenerDecorator } from '../../helpers'; + +export const SceneLeave = createSceneListenerDecorator('leave'); diff --git a/lib/decorators/settings.decorator.ts b/lib/decorators/settings.decorator.ts deleted file mode 100644 index 674af29..0000000 --- a/lib/decorators/settings.decorator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -/** - * Handler for /settings command. - * - * @see https://telegraf.js.org/#/?id=settings - */ -export const Settings = (): MethodDecorator => { - return SetMetadata(DECORATORS.SETTINGS, {}); -}; - -/** - * Handler for /settings command. - * - * @see https://telegraf.js.org/#/?id=settings - * @deprecated since v2, use Settings decorator instead. - */ -export const TelegrafSettings = Settings; diff --git a/lib/decorators/start.decorator.ts b/lib/decorators/start.decorator.ts deleted file mode 100644 index d11658e..0000000 --- a/lib/decorators/start.decorator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -/** - * Handler for /start command. - * - * @see https://telegraf.js.org/#/?id=start - */ -export const Start = (): MethodDecorator => { - return SetMetadata(DECORATORS.START, {}); -}; - -/** - * Handler for /start command. - * - * @see https://telegraf.js.org/#/?id=start - * @deprecated since v2, use Start decorator instead. - */ -export const TelegrafStart = Start; diff --git a/lib/decorators/update-hooks.decorators.ts b/lib/decorators/update-hooks.decorators.ts deleted file mode 100644 index 75621d8..0000000 --- a/lib/decorators/update-hooks.decorators.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; -import * as tt from 'telegraf/typings/telegram-types'; - -export interface UpdateHookOptions { - updateType: - | tt.UpdateType - | tt.UpdateType[] - | tt.MessageSubTypes - | tt.MessageSubTypes[]; -} - -/** - * New incoming message of any kind — text, photo, sticker, etc. - * @constructor - */ -export const Message = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'message', - }); -}; - -/** - * New version of a message that is known to the bot and was edited - * @constructor - */ -export const EditedMessage = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'edited_message', - }); -}; - -/** - * New incoming channel post of any kind — text, photo, sticker, etc. - * @constructor - */ -export const ChannelPost = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'channel_post', - }); -}; - -/** - * New version of a channel post that is known to the bot and was edited - * @constructor - */ -export const EditedChannelPost = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'edited_channel_post', - }); -}; - -/** - * New incoming inline query - * See this decorator in inline-query.decorator.ts - * @constructor - */ -// export const InlineQuery = (): MethodDecorator => { -// return SetMetadata(DECORATORS.UPDATE_HOOK, { -// updateType: 'inline_query', -// }); -// }; - -/** - * The result of an inline query that was chosen by a user and sent to their chat partner. - * @constructor - */ -export const ChosenInlineResult = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'chosen_inline_result', - }); -}; - -/** - * New incoming callback query - * @constructor - */ -export const CallbackQuery = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'callback_query', - }); -}; - -/** - * New incoming shipping query. Only for invoices with flexible price - * @constructor - */ -export const ShippingQuery = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'shipping_query', - }); -}; - -/** - * New incoming pre-checkout query. Contains full information about checkout - * @constructor - */ -export const PreCheckoutQuery = (): MethodDecorator => { - return SetMetadata(DECORATORS.UPDATE_HOOK, { - updateType: 'pre_checkout_query', - }); -}; - -// Two more decorators are missing here. For 'poll' and 'poll_answer' update types. diff --git a/lib/decorators/use.decorator.ts b/lib/decorators/use.decorator.ts deleted file mode 100644 index 8c47461..0000000 --- a/lib/decorators/use.decorator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { DECORATORS } from '../telegraf.constants'; - -/** - * Registers a middleware. - * - * @see https://telegraf.js.org/#/?id=use - */ -export const Use = (): MethodDecorator => { - return SetMetadata(DECORATORS.USE, {}); -}; - -/** - * Registers a middleware. - * - * @see https://telegraf.js.org/#/?id=use - * @deprecated since v2, use Use decorator instead. - */ -export const TelegrafUse = Use; diff --git a/lib/helpers/create-scene-listener-decorator.helper.ts b/lib/helpers/create-scene-listener-decorator.helper.ts new file mode 100644 index 0000000..b089ebf --- /dev/null +++ b/lib/helpers/create-scene-listener-decorator.helper.ts @@ -0,0 +1,18 @@ +import { SetMetadata } from '@nestjs/common'; +import { BaseScene as Scene } from 'telegraf'; +import { ComposerMethodArgs, SceneMethods } from '../types'; +import { UPDATE_LISTENER_METADATA } from '../telegraf.constants'; +import { ListenerMetadata } from '../interfaces'; + +export function createSceneListenerDecorator( + method: Method, +) { + return ( + ...args: ComposerMethodArgs, Method> + ): MethodDecorator => { + return SetMetadata(UPDATE_LISTENER_METADATA, { + method, + args, + } as ListenerMetadata); + }; +} diff --git a/lib/helpers/create-update-listener-decorator.helper.ts b/lib/helpers/create-update-listener-decorator.helper.ts new file mode 100644 index 0000000..28343e2 --- /dev/null +++ b/lib/helpers/create-update-listener-decorator.helper.ts @@ -0,0 +1,18 @@ +import { SetMetadata } from '@nestjs/common'; +import { Composer } from 'telegraf'; +import { ComposerMethodArgs, UpdateMethods } from '../types'; +import { UPDATE_LISTENER_METADATA } from '../telegraf.constants'; +import { ListenerMetadata } from '../interfaces'; + +export function createUpdateListenerDecorator( + method: unknown, +) { + return ( + ...args: ComposerMethodArgs, Method> + ): MethodDecorator => { + return SetMetadata(UPDATE_LISTENER_METADATA, { + method, + args, + } as ListenerMetadata); + }; +} diff --git a/lib/helpers/index.ts b/lib/helpers/index.ts new file mode 100644 index 0000000..20b6077 --- /dev/null +++ b/lib/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './create-update-listener-decorator.helper'; +export * from './create-scene-listener-decorator.helper'; diff --git a/lib/index.ts b/lib/index.ts index 39b657d..ace41d2 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -8,5 +8,8 @@ export * as Extra from 'telegraf/extra'; export * from './decorators'; export * from './interfaces'; +export * from './helpers'; +export * from './utils'; export * from './telegraf.module'; -export * from './telegraf.provider'; +export * from './types'; +export { Telegraf } from 'telegraf'; diff --git a/lib/interfaces/context.interface.ts b/lib/interfaces/context.interface.ts deleted file mode 100644 index 418bbac..0000000 --- a/lib/interfaces/context.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TelegrafContext } from 'telegraf/typings/context'; - -export interface Context extends TelegrafContext { - [key: string]: any; -} - -/** - * Removed type from Telegraf v3.38.0, added for backward compatibility. - * TODO: remove on next major release - */ -export interface ContextMessageUpdate extends Context {} diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts index 3b25926..6be0df9 100644 --- a/lib/interfaces/index.ts +++ b/lib/interfaces/index.ts @@ -1,2 +1,3 @@ -export * from './context.interface'; export * from './telegraf-options.interface'; +export * from './listener-metadata.interface'; +export * from './update-metadata.interface'; diff --git a/lib/interfaces/listener-metadata.interface.ts b/lib/interfaces/listener-metadata.interface.ts new file mode 100644 index 0000000..9dc4925 --- /dev/null +++ b/lib/interfaces/listener-metadata.interface.ts @@ -0,0 +1,4 @@ +export interface ListenerMetadata { + method: string; + args: unknown[]; +} diff --git a/lib/interfaces/telegraf-options.interface.ts b/lib/interfaces/telegraf-options.interface.ts index 13c729d..cfdb594 100644 --- a/lib/interfaces/telegraf-options.interface.ts +++ b/lib/interfaces/telegraf-options.interface.ts @@ -1,17 +1,21 @@ import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; +import { Middleware, Context } from 'telegraf'; import { TelegrafOptions, LaunchPollingOptions, LaunchWebhookOptions, } from 'telegraf/typings/telegraf'; -export interface TelegrafModuleOptions { +export interface TelegrafModuleOptions { token: string; options?: TelegrafOptions; launchOptions?: { polling?: LaunchPollingOptions; webhook?: LaunchWebhookOptions; }; + botName?: string; + include?: Function[]; + middlewares?: ReadonlyArray>; } export interface TelegrafOptionsFactory { @@ -20,6 +24,7 @@ export interface TelegrafOptionsFactory { export interface TelegrafModuleAsyncOptions extends Pick { + botName?: string; useExisting?: Type; useClass?: Type; useFactory?: ( diff --git a/lib/interfaces/update-metadata.interface.ts b/lib/interfaces/update-metadata.interface.ts new file mode 100644 index 0000000..4c2ba0e --- /dev/null +++ b/lib/interfaces/update-metadata.interface.ts @@ -0,0 +1,6 @@ +export interface UpdateMetadata { + name: string; + type: string; + methodName: string; + callback?: Function | Record; +} diff --git a/lib/services/base-explorer.service.ts b/lib/services/base-explorer.service.ts new file mode 100644 index 0000000..eff7c82 --- /dev/null +++ b/lib/services/base-explorer.service.ts @@ -0,0 +1,59 @@ +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { Module } from '@nestjs/core/injector/module'; +import { flattenDeep, groupBy, identity, isEmpty, mapValues } from 'lodash'; +import { UpdateMetadata } from '../interfaces'; + +export class BaseExplorerService { + getModules( + modulesContainer: Map, + include: Function[], + ): Module[] { + if (!include || isEmpty(include)) { + return [...modulesContainer.values()]; + } + const whitelisted = this.includeWhitelisted(modulesContainer, include); + return whitelisted; + } + + includeWhitelisted( + modulesContainer: Map, + include: Function[], + ): Module[] { + const modules = [...modulesContainer.values()]; + return modules.filter(({ metatype }) => + include.some((item) => item === metatype), + ); + } + + flatMap( + modules: Module[], + callback: (instance: InstanceWrapper, moduleRef: Module) => T | T[], + ): T[] { + const invokeMap = () => { + return modules.map((moduleRef) => { + const providers = [...moduleRef.providers.values()]; + return providers.map((wrapper) => callback(wrapper, moduleRef)); + }); + }; + return flattenDeep(invokeMap()).filter(identity); + } + + groupMetadata(resolvers: UpdateMetadata[]) { + const groupByType = groupBy( + resolvers, + (metadata: UpdateMetadata) => metadata.type, + ); + const groupedMetadata = mapValues( + groupByType, + (resolversArr: UpdateMetadata[]) => + resolversArr.reduce( + (prev, curr) => ({ + ...prev, + [curr.name]: curr.callback, + }), + {}, + ), + ); + return groupedMetadata; + } +} diff --git a/lib/services/index.ts b/lib/services/index.ts new file mode 100644 index 0000000..9859475 --- /dev/null +++ b/lib/services/index.ts @@ -0,0 +1,4 @@ +export * from './updates-explorer.service'; +export * from './metadata-accessor.service'; +export * from './scenes-explorer.service'; +export * from './updates-explorer.service'; diff --git a/lib/services/metadata-accessor.service.ts b/lib/services/metadata-accessor.service.ts new file mode 100644 index 0000000..3e7e044 --- /dev/null +++ b/lib/services/metadata-accessor.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { + SCENE_METADATA, + UPDATE_LISTENER_METADATA, + UPDATE_METADATA, +} from '../telegraf.constants'; +import { ListenerMetadata } from '../interfaces'; + +@Injectable() +export class MetadataAccessorService { + constructor(private readonly reflector: Reflector) {} + + isUpdate(target: Function): boolean { + if (!target) { + return false; + } + return !!this.reflector.get(UPDATE_METADATA, target); + } + + isUpdateListener(target: Function) { + if (!target) { + return false; + } + return !!this.reflector.get(UPDATE_LISTENER_METADATA, target); + } + + isScene(target: Function): boolean { + if (!target) { + return false; + } + return !!this.reflector.get(SCENE_METADATA, target); + } + + getListenerMetadata(target: Function): ListenerMetadata | undefined { + return this.reflector.get(UPDATE_LISTENER_METADATA, target); + } + + getSceneMetadata(target: Function): string | undefined { + return this.reflector.get(SCENE_METADATA, target); + } +} diff --git a/lib/services/scenes-explorer.service.ts b/lib/services/scenes-explorer.service.ts new file mode 100644 index 0000000..be3d47d --- /dev/null +++ b/lib/services/scenes-explorer.service.ts @@ -0,0 +1,98 @@ +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService, ModuleRef, ModulesContainer } from '@nestjs/core'; +import { MetadataScanner } from '@nestjs/core/metadata-scanner'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { BaseScene as Scene, Stage, Telegraf } from 'telegraf'; +import { MetadataAccessorService } from './metadata-accessor.service'; +import { BaseExplorerService } from './base-explorer.service'; +import { + TELEGRAF_BOT_NAME, + TELEGRAF_MODULE_OPTIONS, +} from '../telegraf.constants'; +import { TelegrafModuleOptions } from '../interfaces'; +import { Module } from '@nestjs/core/injector/module'; + +@Injectable() +export class ScenesExplorerService + extends BaseExplorerService + implements OnModuleInit { + private readonly stage = new Stage([]); + + constructor( + @Inject(TELEGRAF_BOT_NAME) + private readonly botName: string, + @Inject(TELEGRAF_MODULE_OPTIONS) + private readonly telegrafModuleOptions: TelegrafModuleOptions, + private readonly moduleRef: ModuleRef, + private readonly discoveryService: DiscoveryService, + private readonly metadataAccessor: MetadataAccessorService, + private readonly metadataScanner: MetadataScanner, + private readonly modulesContainer: ModulesContainer, + ) { + super(); + } + + private bot: Telegraf; + + onModuleInit(): void { + this.bot = this.moduleRef.get>(this.botName, { + strict: false, + }); + this.bot.use(this.stage.middleware()); + this.explore(); + } + + private explore(): void { + const modules = this.getModules( + this.modulesContainer, + this.telegrafModuleOptions.include || [], + ); + const scenes = this.flatMap(modules, (instance, moduleRef) => + this.applyScenes(instance, moduleRef), + ); + } + + private applyScenes(wrapper: InstanceWrapper, moduleRef: Module) { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + const prototype = Object.getPrototypeOf(instance); + + const providers: InstanceWrapper[] = this.discoveryService.getProviders(); + const sceneProviders: InstanceWrapper[] = providers.filter( + (wrapper: InstanceWrapper) => + this.metadataAccessor.isScene(wrapper.metatype), + ); + + sceneProviders.forEach((wrapper) => { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + + const sceneId = this.metadataAccessor.getSceneMetadata( + instance.constructor, + ); + + const scene = new Scene(sceneId); + this.stage.register(scene); + + this.metadataScanner.scanFromPrototype( + instance, + prototype, + (methodKey: string) => { + const methodRef = instance[methodKey]; + if (this.metadataAccessor.isUpdateListener(methodRef)) { + const metadata = this.metadataAccessor.getListenerMetadata( + methodRef, + ); + const middlewareFn = methodRef.bind(instance); + const { method, args } = metadata; + (scene[method] as any)(...args, middlewareFn); + } + }, + ); + }); + } +} diff --git a/lib/services/updates-explorer.service.ts b/lib/services/updates-explorer.service.ts new file mode 100644 index 0000000..09fa250 --- /dev/null +++ b/lib/services/updates-explorer.service.ts @@ -0,0 +1,83 @@ +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService, ModuleRef, ModulesContainer } from '@nestjs/core'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { MetadataScanner } from '@nestjs/core/metadata-scanner'; +import { MetadataAccessorService } from './metadata-accessor.service'; +import { + TELEGRAF_BOT_NAME, + TELEGRAF_MODULE_OPTIONS, +} from '../telegraf.constants'; +import { Telegraf } from 'telegraf'; +import { TelegrafModuleOptions } from '../interfaces'; +import { BaseExplorerService } from './base-explorer.service'; +import { Module } from '@nestjs/core/injector/module'; + +@Injectable() +export class UpdatesExplorerService + extends BaseExplorerService + implements OnModuleInit { + constructor( + @Inject(TELEGRAF_BOT_NAME) + private readonly botName: string, + @Inject(TELEGRAF_MODULE_OPTIONS) + private readonly telegrafModuleOptions: TelegrafModuleOptions, + private readonly moduleRef: ModuleRef, + private readonly discoveryService: DiscoveryService, + private readonly metadataAccessor: MetadataAccessorService, + private readonly metadataScanner: MetadataScanner, + private readonly modulesContainer: ModulesContainer, + ) { + super(); + } + + private bot: Telegraf; + + onModuleInit(): void { + this.bot = this.moduleRef.get>(this.botName, { + strict: false, + }); + this.explore(); + } + + explore() { + const modules = this.getModules( + this.modulesContainer, + this.telegrafModuleOptions.include || [], + ); + const updates = this.flatMap(modules, (instance, moduleRef) => + this.applyUpdates(instance, moduleRef), + ); + } + + private applyUpdates(wrapper: InstanceWrapper, moduleRef: Module) { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + const prototype = Object.getPrototypeOf(instance); + + const providers: InstanceWrapper[] = this.discoveryService.getProviders(); + const updateProviders: InstanceWrapper[] = providers.filter( + (wrapper: InstanceWrapper) => + this.metadataAccessor.isUpdate(wrapper.metatype), + ); + + updateProviders.forEach((wrapper: InstanceWrapper) => { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + this.metadataScanner.scanFromPrototype(instance, prototype, (name) => { + const methodRef = instance[name]; + if (this.metadataAccessor.isUpdateListener(methodRef)) { + const metadata = this.metadataAccessor.getListenerMetadata(methodRef); + const middlewareFn = methodRef.bind(instance); + const { method, args } = metadata; + // NOTE: Use "any" to disable "Expected at least 1 arguments, but got 1 or more." error. + // Use telegraf instance for non-scene listeners + (this.bot[method] as any)(...args, middlewareFn); + } + }); + }); + } +} diff --git a/lib/telegraf-core.module.ts b/lib/telegraf-core.module.ts index 9be69ed..f33d5cc 100644 --- a/lib/telegraf-core.module.ts +++ b/lib/telegraf-core.module.ts @@ -1,56 +1,126 @@ -import { DiscoveryModule } from '@nestjs/core'; -import { Module, DynamicModule, Provider, Type } from '@nestjs/common'; +import { DiscoveryModule, ModuleRef } from '@nestjs/core'; +import { + Module, + DynamicModule, + Provider, + Type, + Global, + Inject, + OnApplicationShutdown, + Logger, +} from '@nestjs/common'; import { TelegrafModuleOptions, TelegrafModuleAsyncOptions, TelegrafOptionsFactory, } from './interfaces'; import { + TELEGRAF_BOT_NAME, TELEGRAF_MODULE_OPTIONS, - TELEGRAF_PROVIDER, } from './telegraf.constants'; -import { TelegrafMetadataAccessor } from './telegraf-metadata.accessor'; -import { TelegrafExplorer } from './telegraf.explorer'; -import { TelegrafProvider } from './telegraf.provider'; +import { + MetadataAccessorService, + ScenesExplorerService, + UpdatesExplorerService, +} from './services'; +import { getBotToken } from './utils'; +import { Telegraf } from 'telegraf'; +import { defer } from 'rxjs'; +@Global() @Module({ imports: [DiscoveryModule], - providers: [TelegrafMetadataAccessor, TelegrafExplorer], + providers: [ + UpdatesExplorerService, + ScenesExplorerService, + MetadataAccessorService, + ], }) -export class TelegrafCoreModule { +export class TelegrafCoreModule implements OnApplicationShutdown { + private static logger = new Logger(TelegrafCoreModule.name); + + constructor( + @Inject(TELEGRAF_BOT_NAME) private readonly botName: string, + private readonly moduleRef: ModuleRef, + ) {} + public static forRoot(options: TelegrafModuleOptions): DynamicModule { - const telegrafProvider = { - provide: TELEGRAF_PROVIDER, - useClass: TelegrafProvider, - inject: [TELEGRAF_MODULE_OPTIONS], + const telegrafBotName = getBotToken(options.botName); + + const telegrafBotProvider = { + provide: telegrafBotName, + useFactory: async (): Promise => + await defer(async () => { + const bot = new Telegraf(options.token); + this.applyBotMiddlewares(bot, options.middlewares); + await bot.launch(options.launchOptions); + return bot; + }).toPromise(), }; + return { module: TelegrafCoreModule, providers: [ - { provide: TELEGRAF_MODULE_OPTIONS, useValue: options }, - telegrafProvider, + { + provide: TELEGRAF_MODULE_OPTIONS, + useValue: options, + }, + { + provide: TELEGRAF_BOT_NAME, + useValue: telegrafBotName, + }, + telegrafBotProvider, ], - exports: [telegrafProvider], + exports: [telegrafBotProvider], }; } public static forRootAsync( options: TelegrafModuleAsyncOptions, ): DynamicModule { - const telegrafProvider = { - provide: TELEGRAF_PROVIDER, - useClass: TelegrafProvider, + const telegrafBotName = getBotToken(options.botName); + + const telegrafBotProvider = { + provide: telegrafBotName, + useFactory: async ( + telegrafModuleOptions: TelegrafModuleOptions, + ): Promise => { + const { botName, ...telegrafOptions } = telegrafModuleOptions; + + return await defer(async () => { + const bot = new Telegraf(telegrafOptions.token); + this.applyBotMiddlewares(bot, telegrafOptions.middlewares); + await bot.launch(telegrafOptions.launchOptions); + return bot; + }).toPromise(); + }, inject: [TELEGRAF_MODULE_OPTIONS], }; + const asyncProviders = this.createAsyncProviders(options); return { module: TelegrafCoreModule, imports: options.imports, - providers: [...asyncProviders, telegrafProvider], - exports: [telegrafProvider], + providers: [ + ...asyncProviders, + { + provide: TELEGRAF_BOT_NAME, + useValue: telegrafBotName, + }, + telegrafBotProvider, + ], + exports: [telegrafBotProvider], }; } + private static applyBotMiddlewares(bot, middlewares) { + if (middlewares) { + middlewares.forEach((middleware) => { + bot.use(middleware); + }); + } + } + private static createAsyncProviders( options: TelegrafModuleAsyncOptions, ): Provider[] { @@ -88,4 +158,9 @@ export class TelegrafCoreModule { inject, }; } + + async onApplicationShutdown(): Promise { + const bot = this.moduleRef.get(this.botName); + bot && (await bot.stop()); + } } diff --git a/lib/telegraf-metadata.accessor.ts b/lib/telegraf-metadata.accessor.ts deleted file mode 100644 index b204d60..0000000 --- a/lib/telegraf-metadata.accessor.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { Injectable, Type } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { - ActionOptions, - CashtagOptions, - CommandOptions, - EntityOptions, - HashtagOptions, - HearsOptions, - InlineQueryOptions, - MentionOptions, - OnOptions, - PhoneOptions, - UpdateHookOptions, -} from './decorators'; -import { DECORATORS } from './telegraf.constants'; - -@Injectable() -export class TelegrafMetadataAccessor { - constructor(private readonly reflector: Reflector) {} - - isUpdate(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.UPDATE, target); - } - - isUpdateHook(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.UPDATE_HOOK, target); - } - - getUpdateHookMetadata( - target: Type | Function, - ): UpdateHookOptions | undefined { - return this.reflector.get(DECORATORS.UPDATE_HOOK, target); - } - - isTelegrafUse(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.USE, target); - } - - isTelegrafOn(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.ON, target); - } - - getTelegrafOnMetadata(target: Type | Function): OnOptions | undefined { - return this.reflector.get(DECORATORS.ON, target); - } - - isTelegrafHears(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.HEARS, target); - } - - getTelegrafHearsMetadata( - target: Type | Function, - ): HearsOptions | undefined { - return this.reflector.get(DECORATORS.HEARS, target); - } - - isTelegrafCommand(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.COMMAND, target); - } - - getTelegrafCommandMetadata( - target: Type | Function, - ): CommandOptions | undefined { - return this.reflector.get(DECORATORS.COMMAND, target); - } - - isTelegrafStart(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.START, target); - } - - isTelegrafHelp(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.HELP, target); - } - - isTelegrafSettings(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.SETTINGS, target); - } - - isTelegrafEntity(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.ENTITY, target); - } - - getTelegrafEntityMetadata( - target: Type | Function, - ): EntityOptions | undefined { - return this.reflector.get(DECORATORS.ENTITY, target); - } - - isTelegrafMention(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.MENTION, target); - } - - getTelegrafMentionMetadata( - target: Type | Function, - ): MentionOptions | undefined { - return this.reflector.get(DECORATORS.MENTION, target); - } - - isTelegrafPhone(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.PHONE, target); - } - - getTelegrafPhoneMetadata( - target: Type | Function, - ): PhoneOptions | undefined { - return this.reflector.get(DECORATORS.PHONE, target); - } - - isTelegrafHashtag(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.HASHTAG, target); - } - - getTelegrafHashtagMetadata( - target: Type | Function, - ): HashtagOptions | undefined { - return this.reflector.get(DECORATORS.HASHTAG, target); - } - - isTelegrafCashtag(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.CASHTAG, target); - } - - getTelegrafCashtagMetadata( - target: Type | Function, - ): CashtagOptions | undefined { - return this.reflector.get(DECORATORS.CASHTAG, target); - } - - isTelegrafAction(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.ACTION, target); - } - - getTelegrafActionMetadata( - target: Type | Function, - ): ActionOptions | undefined { - return this.reflector.get(DECORATORS.ACTION, target); - } - - isTelegrafInlineQuery(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.INLINE_QUERY, target); - } - - getTelegrafInlineQueryMetadata( - target: Type | Function, - ): InlineQueryOptions | undefined { - return this.reflector.get(DECORATORS.INLINE_QUERY, target); - } - - isTelegrafGameQuery(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.GAME_QUERY, target); - } -} diff --git a/lib/telegraf.constants.ts b/lib/telegraf.constants.ts index 1affe84..3ac37e3 100644 --- a/lib/telegraf.constants.ts +++ b/lib/telegraf.constants.ts @@ -1,23 +1,8 @@ export const TELEGRAF_MODULE_OPTIONS = 'TELEGRAF_MODULE_OPTIONS'; -export const TELEGRAF_PROVIDER = 'TelegrafProvider'; +export const TELEGRAF_BOT_NAME = 'TELEGRAF_BOT_NAME'; +export const DEFAULT_BOT_NAME = 'DEFAULT_BOT_NAME'; -export const DECORATORS_PREFIX = 'TELEGRAF'; -export const DECORATORS = { - USE: `${DECORATORS_PREFIX}/USE`, - ON: `${DECORATORS_PREFIX}/ON`, - HEARS: `${DECORATORS_PREFIX}/HEARS`, - COMMAND: `${DECORATORS_PREFIX}/COMMAND`, - START: `${DECORATORS_PREFIX}/START`, - HELP: `${DECORATORS_PREFIX}/HELP`, - SETTINGS: `${DECORATORS_PREFIX}/SETTINGS`, - ENTITY: `${DECORATORS_PREFIX}/ENTITY`, - MENTION: `${DECORATORS_PREFIX}/MENTION`, - PHONE: `${DECORATORS_PREFIX}/PHONE`, - HASHTAG: `${DECORATORS_PREFIX}/HASHTAG`, - CASHTAG: `${DECORATORS_PREFIX}/CASHTAG`, - ACTION: `${DECORATORS_PREFIX}/ACTION`, - INLINE_QUERY: `${DECORATORS_PREFIX}/INLINE_QUERY`, - GAME_QUERY: `${DECORATORS_PREFIX}/GAME_QUERY`, - UPDATE: `${DECORATORS_PREFIX}/UPDATE`, - UPDATE_HOOK: `${DECORATORS_PREFIX}/UPDATE_HOOK`, -}; +export const UPDATE_METADATA = 'UPDATE_METADATA'; +export const UPDATE_LISTENER_METADATA = 'UPDATE_LISTENER_METADATA'; + +export const SCENE_METADATA = 'SCENE_METADATA'; diff --git a/lib/telegraf.explorer.ts b/lib/telegraf.explorer.ts deleted file mode 100644 index 4ddefd6..0000000 --- a/lib/telegraf.explorer.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; -import { DiscoveryService, ModuleRef } from '@nestjs/core'; -import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; -import { MetadataScanner } from '@nestjs/core/metadata-scanner'; -import { TelegrafMetadataAccessor } from './telegraf-metadata.accessor'; -import { TelegrafProvider } from './telegraf.provider'; -import { TELEGRAF_PROVIDER } from './telegraf.constants'; -import { - ActionOptions, - CashtagOptions, - CommandOptions, - EntityOptions, - HashtagOptions, - HearsOptions, - InlineQueryOptions, - MentionOptions, - OnOptions, - PhoneOptions, - UpdateHookOptions, -} from './decorators'; - -@Injectable() -export class TelegrafExplorer implements OnModuleInit { - constructor( - private readonly moduleRef: ModuleRef, - private readonly discoveryService: DiscoveryService, - private readonly metadataAccessor: TelegrafMetadataAccessor, - private readonly metadataScanner: MetadataScanner, - ) {} - - private telegraf: TelegrafProvider; - - onModuleInit() { - this.telegraf = this.moduleRef.get(TELEGRAF_PROVIDER, { - strict: false, - }); - this.explore(); - } - - explore() { - /** - * Update providers section is only for decorators under Update decorator - */ - const updateProviders: InstanceWrapper[] = this.discoveryService - .getProviders() - .filter((wrapper: InstanceWrapper) => - this.metadataAccessor.isUpdate(wrapper.metatype), - ); - - updateProviders.forEach((wrapper: InstanceWrapper) => { - const { instance } = wrapper; - - this.metadataScanner.scanFromPrototype( - instance, - Object.getPrototypeOf(instance), - (key: string) => { - if (this.metadataAccessor.isUpdateHook(instance[key])) { - const metadata = this.metadataAccessor.getUpdateHookMetadata( - instance[key], - ); - this.handleUpdateHook(instance, key, metadata); - } - }, - ); - }); - - const providers: InstanceWrapper[] = this.discoveryService.getProviders(); - - providers.forEach((wrapper: InstanceWrapper) => { - const { instance } = wrapper; - - if (!instance) { - return; - } - - this.metadataScanner.scanFromPrototype( - instance, - Object.getPrototypeOf(instance), - (key: string) => { - if (this.metadataAccessor.isTelegrafUse(instance[key])) { - this.handleTelegrafUse(instance, key); - } else if (this.metadataAccessor.isTelegrafOn(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafOnMetadata( - instance[key], - ); - this.handleTelegrafOn(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafHears(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafHearsMetadata( - instance[key], - ); - this.handleTelegrafHears(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafCommand(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafCommandMetadata( - instance[key], - ); - this.handleTelegrafCommand(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafStart(instance[key])) { - this.handleTelegrafStart(instance, key); - } else if (this.metadataAccessor.isTelegrafHelp(instance[key])) { - this.handleTelegrafHelp(instance, key); - } else if (this.metadataAccessor.isTelegrafSettings(instance[key])) { - this.handleTelegrafSettings(instance, key); - } else if (this.metadataAccessor.isTelegrafEntity(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafEntityMetadata( - instance[key], - ); - this.handleTelegrafEntity(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafMention(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafMentionMetadata( - instance[key], - ); - this.handleTelegrafMention(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafPhone(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafPhoneMetadata( - instance[key], - ); - this.handleTelegrafPhone(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafHashtag(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafHashtagMetadata( - instance[key], - ); - this.handleTelegrafHashtag(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafCashtag(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafCashtagMetadata( - instance[key], - ); - this.handleTelegrafCashtag(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafAction(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafActionMetadata( - instance[key], - ); - this.handleTelegrafAction(instance, key, metadata); - } else if ( - this.metadataAccessor.isTelegrafInlineQuery(instance[key]) - ) { - const metadata = this.metadataAccessor.getTelegrafInlineQueryMetadata( - instance[key], - ); - this.handleTelegrafInlineQuery(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafGameQuery(instance[key])) { - this.handleTelegrafGameQuery(instance, key); - } - }, - ); - }); - } - - handleUpdateHook(instance: object, key: string, metadata: UpdateHookOptions) { - this.telegraf.on(metadata.updateType, instance[key].bind(instance)); - } - - handleTelegrafUse(instance: object, key: string) { - this.telegraf.use(instance[key].bind(instance)); - } - - handleTelegrafOn(instance: object, key: string, metadata: OnOptions) { - this.telegraf.on(metadata.updateTypes, instance[key].bind(instance)); - } - - handleTelegrafHears(instance: object, key: string, metadata: HearsOptions) { - this.telegraf.hears(metadata.triggers, instance[key].bind(instance)); - } - - handleTelegrafCommand( - instance: object, - key: string, - metadata: CommandOptions, - ) { - this.telegraf.command(metadata.commands, instance[key].bind(instance)); - } - - handleTelegrafStart(instance: object, key: string) { - this.telegraf.start(instance[key].bind(instance)); - } - - handleTelegrafHelp(instance: object, key: string) { - this.telegraf.help(instance[key].bind(instance)); - } - - handleTelegrafSettings(instance: object, key: string) { - // @ts-ignore - this.telegraf.settings(instance[key].bind(instance)); - } - - handleTelegrafEntity(instance: object, key: string, metadata: EntityOptions) { - // @ts-ignore - this.telegraf.entity(metadata.entity, instance[key].bind(instance)); - } - - handleTelegrafMention( - instance: object, - key: string, - metadata: MentionOptions, - ) { - // @ts-ignore - this.telegraf.mention(metadata.username, instance[key].bind(instance)); - } - - handleTelegrafPhone(instance: object, key: string, metadata: PhoneOptions) { - // @ts-ignore - this.telegraf.phone(metadata.phone, instance[key].bind(instance)); - } - - handleTelegrafHashtag( - instance: object, - key: string, - metadata: HashtagOptions, - ) { - // @ts-ignore - this.telegraf.hashtag(metadata.hashtag, instance[key].bind(instance)); - } - - handleTelegrafCashtag( - instance: object, - key: string, - metadata: CashtagOptions, - ) { - // @ts-ignore - this.telegraf.cashtag(metadata.cashtag, instance[key].bind(instance)); - } - - handleTelegrafAction(instance: object, key: string, metadata: ActionOptions) { - this.telegraf.action(metadata.triggers, instance[key].bind(instance)); - } - - handleTelegrafInlineQuery( - instance: object, - key: string, - metadata: InlineQueryOptions, - ) { - if (metadata.triggers) { - // @ts-ignore - this.telegraf.inlineQuery( - metadata.triggers, - instance[key].bind(instance), - ); - } else { - this.telegraf.on(metadata.updateType, instance[key].bind(instance)); - } - } - - handleTelegrafGameQuery(instance: object, key: string) { - this.telegraf.gameQuery(instance[key].bind(instance)); - } -} diff --git a/lib/telegraf.provider.ts b/lib/telegraf.provider.ts deleted file mode 100644 index 58be5b1..0000000 --- a/lib/telegraf.provider.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - Injectable, - Inject, - OnApplicationBootstrap, - Logger, - OnApplicationShutdown, -} from '@nestjs/common'; -import { Telegraf } from 'telegraf'; -import { Context, TelegrafModuleOptions } from './interfaces'; -import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants'; - -@Injectable() -export class TelegrafProvider extends Telegraf - implements OnApplicationBootstrap, OnApplicationShutdown { - private logger = new Logger('Telegraf'); - private readonly launchOptions; - - constructor(@Inject(TELEGRAF_MODULE_OPTIONS) options: TelegrafModuleOptions) { - super(options.token, options.options); - this.launchOptions = options.launchOptions; - } - - async onApplicationBootstrap() { - this.catch((err, ctx: Context) => { - this.logger.error( - `Encountered an error for ${ctx.updateType} update type`, - err, - ); - }); - - await this.launch(this.launchOptions); - } - - async onApplicationShutdown() { - await this.stop(); - } -} diff --git a/lib/types/index.ts b/lib/types/index.ts new file mode 100644 index 0000000..4a6dc63 --- /dev/null +++ b/lib/types/index.ts @@ -0,0 +1,21 @@ +import { BaseScene, Composer, Middleware } from 'telegraf'; + +export type Filter = T extends [] + ? [] + : T extends [infer Head, ...infer Tail] + ? Head extends F + ? Filter + : [Head, ...Filter] + : []; + +export type OnlyFunctionPropertyNames = { + [K in keyof T]: T[K] extends (...args: any) => any ? K : never; +}[keyof T]; + +export type ComposerMethodArgs< + T extends Composer, + U extends OnlyFunctionPropertyNames = OnlyFunctionPropertyNames +> = Filter, Middleware>; + +export type UpdateMethods = OnlyFunctionPropertyNames>; +export type SceneMethods = OnlyFunctionPropertyNames>; diff --git a/lib/utils/get-bot-token.util.ts b/lib/utils/get-bot-token.util.ts new file mode 100644 index 0000000..a8458e8 --- /dev/null +++ b/lib/utils/get-bot-token.util.ts @@ -0,0 +1,7 @@ +import { DEFAULT_BOT_NAME } from '../telegraf.constants'; + +export function getBotToken(name?: string) { + return name && name !== DEFAULT_BOT_NAME + ? `${name}_BOT_NAME` + : DEFAULT_BOT_NAME; +} diff --git a/lib/utils/index.ts b/lib/utils/index.ts new file mode 100644 index 0000000..9dc7d1d --- /dev/null +++ b/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './get-bot-token.util'; diff --git a/package-lock.json b/package-lock.json index 54659a1..acbfe97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "nestjs-telegraf", - "version": "1.3.1", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.3.1", + "version": "2.0.0", "license": "MIT", "dependencies": { - "telegraf": "3.38.0" + "telegraf": "^3.38.0" }, "devDependencies": { "@nestjs/common": "7.6.5", "@nestjs/core": "7.6.5", + "@types/lodash": "^4.14.167", "@typescript-eslint/eslint-plugin": "4.11.1", "@typescript-eslint/parser": "4.11.1", "eslint": "7.17.0", @@ -20,6 +21,7 @@ "eslint-plugin-import": "2.22.1", "husky": "4.3.6", "lint-staged": "10.5.3", + "lodash": "^4.17.20", "prettier": "2.2.1", "reflect-metadata": "0.1.13", "rxjs": "6.6.3", @@ -292,6 +294,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3880,6 +3888,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", diff --git a/package.json b/package.json index a8609ce..4bb4b75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nestjs-telegraf", - "version": "1.3.1", + "version": "2.0.0", "description": "Telegraf module for NestJS", "keywords": [ "nest", @@ -32,11 +32,12 @@ "test": "" }, "dependencies": { - "telegraf": "3.38.0" + "telegraf": "^3.38.0" }, "devDependencies": { "@nestjs/common": "7.6.5", "@nestjs/core": "7.6.5", + "@types/lodash": "^4.14.167", "@typescript-eslint/eslint-plugin": "4.11.1", "@typescript-eslint/parser": "4.11.1", "eslint": "7.17.0", @@ -44,6 +45,7 @@ "eslint-plugin-import": "2.22.1", "husky": "4.3.6", "lint-staged": "10.5.3", + "lodash": "^4.17.20", "prettier": "2.2.1", "reflect-metadata": "0.1.13", "rxjs": "6.6.3", diff --git a/sample/app.constants.ts b/sample/app.constants.ts new file mode 100644 index 0000000..b9cfdda --- /dev/null +++ b/sample/app.constants.ts @@ -0,0 +1 @@ +export const HELLO_SCENE_ID = 'HELLO_SCENE_ID'; diff --git a/sample/app.module.ts b/sample/app.module.ts new file mode 100644 index 0000000..2d177de --- /dev/null +++ b/sample/app.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { TelegrafModule } from '../lib'; +import { EchoService } from './echo.service'; +import { AppUpdate } from './app.update'; +import { HelloScene } from './scenes/hello.scene'; +import { sessionMiddleware } from './middleware/session.middleware'; + +@Module({ + imports: [ + TelegrafModule.forRoot({ + token: '1467731595:AAHCvH65H9VQYKF9jE-E8c2rXsQBVAYseg8', // Don't steal >:( + middlewares: [sessionMiddleware], + }), + ], + providers: [EchoService, AppUpdate, HelloScene], +}) +export class AppModule {} diff --git a/sample/app.update.ts b/sample/app.update.ts new file mode 100644 index 0000000..f3c5022 --- /dev/null +++ b/sample/app.update.ts @@ -0,0 +1,43 @@ +import { Telegraf } from 'telegraf'; +import { Command, Help, InjectBot, On, Start, Update } from '../lib'; +import { EchoService } from './echo.service'; +import { HELLO_SCENE_ID } from './app.constants'; +import { Context } from './interfaces/context.interface'; + +@Update() +export class AppUpdate { + constructor( + @InjectBot() + private readonly bot: Telegraf, // TODO: fix any + private readonly echoService: EchoService, + ) {} + + @Start() + async onStart(ctx: Context): Promise { + const me = await this.bot.telegram.getMe(); + await ctx.reply(`Hey, I'm ${me.first_name}`); + } + + @Help() + async onHelp(ctx: Context): Promise { + await ctx.reply('Send me any text'); + } + + @Command('scene') + async onSceneCommand(ctx: Context): Promise { + await ctx.scene.enter(HELLO_SCENE_ID); + } + + @On('message') + async onMessage(ctx: Context): Promise { + console.log('New message received'); + + if ('text' in ctx.message) { + const messageText = ctx.message.text; + const echoText = this.echoService.echo(messageText); + await ctx.reply(echoText); + } else { + await ctx.reply('Only text messages'); + } + } +} diff --git a/sample/echo.service.ts b/sample/echo.service.ts new file mode 100644 index 0000000..2603cc2 --- /dev/null +++ b/sample/echo.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class EchoService { + echo(text: string): string { + return `Echo: ${text}`; + } +} diff --git a/sample/interfaces/context.interface.ts b/sample/interfaces/context.interface.ts new file mode 100644 index 0000000..58f20ef --- /dev/null +++ b/sample/interfaces/context.interface.ts @@ -0,0 +1,4 @@ +import { SceneContextMessageUpdate } from 'telegraf/typings/stage'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Context extends SceneContextMessageUpdate {} diff --git a/sample/main.ts b/sample/main.ts new file mode 100644 index 0000000..42c2fa5 --- /dev/null +++ b/sample/main.ts @@ -0,0 +1,7 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + await NestFactory.createApplicationContext(AppModule); +} +bootstrap(); diff --git a/sample/middleware/session.middleware.ts b/sample/middleware/session.middleware.ts new file mode 100644 index 0000000..c203f15 --- /dev/null +++ b/sample/middleware/session.middleware.ts @@ -0,0 +1,3 @@ +import { session } from 'telegraf'; + +export const sessionMiddleware = session(); diff --git a/sample/scenes/hello.scene.ts b/sample/scenes/hello.scene.ts new file mode 100644 index 0000000..f84a6f4 --- /dev/null +++ b/sample/scenes/hello.scene.ts @@ -0,0 +1,29 @@ +import { HELLO_SCENE_ID } from '../app.constants'; +import { Context } from '../interfaces/context.interface'; +import { Scene, SceneEnter, SceneLeave, Command } from '../../lib'; + +@Scene(HELLO_SCENE_ID) +export class HelloScene { + @SceneEnter() + async onSceneEnter(ctx: Context): Promise { + console.log('Enter to scene'); + await ctx.reply('Welcome on scene ✋'); + } + + @SceneLeave() + async onSceneLeave(ctx: Context): Promise { + console.log('Leave from scene'); + await ctx.reply('Bye Bye 👋'); + } + + @Command('hello') + async onHelloCommand(ctx: Context): Promise { + console.log('Use say hello'); + await ctx.reply('Hi'); + } + + @Command('leave') + async onLeaveCommand(ctx: Context): Promise { + await ctx.scene.leave(); + } +} diff --git a/website/docs/bot-injection.md b/website/docs/bot-injection.md index 8aa67df..316b1e9 100644 --- a/website/docs/bot-injection.md +++ b/website/docs/bot-injection.md @@ -8,10 +8,10 @@ slug: /bot-injection At times you may need to access the native `Telegraf` instance. For example, you may want to connect stage middleware. You can inject the Telegraf by using the `@InjectBot()` decorator as follows: ```typescript import { Injectable } from '@nestjs/common'; -import { InjectBot, TelegrafProvider } from 'nestjs-telegraf'; +import { InjectBot, TelegrafProvider, Context } from 'nestjs-telegraf'; @Injectable() export class BotSettingsService { - constructor(@InjectBot() private bot: TelegrafProvider) {} + constructor(@InjectBot() private bot: TelegrafProvider) {} } ``` diff --git a/website/docs/error-handling.md b/website/docs/error-handling.md new file mode 100644 index 0000000..a684e81 --- /dev/null +++ b/website/docs/error-handling.md @@ -0,0 +1,31 @@ +--- +id: error-handling +title: Error handling +sidebar_label: Error handling +slug: /error-handling +--- + +By default, `nestjs-telegraf` catches all errors using the `Logger` built into NestJS. + +Use can disable global errors catch with `disableGlobalCatch`: +```typescript +TelegrafModule.forRoot({ + disableGlobalCatch: true, +}), +``` + +After that you can override errors handling with bot instance `catch` function. + +```typescript +import { Injectable } from '@nestjs/common'; +import { InjectBot, TelegrafProvider, Context } from 'nestjs-telegraf'; + +@Injectable() +export class BotSettingsService { + constructor(@InjectBot() private bot: TelegrafProvider) { + this.bot.catch((err, ctx) => { + console.log(`Ooops, encountered an error for ${ctx.updateType}`, err); + }); + } +} +``` diff --git a/website/docs/getting-updates.md b/website/docs/getting-updates.md index df6e31a..a42d3fe 100644 --- a/website/docs/getting-updates.md +++ b/website/docs/getting-updates.md @@ -11,23 +11,26 @@ By default, the bot receives updates using long-polling and requires no addition ## Webhooks -If you want to configure a telegram bot webhook, you need to get a middleware from `TelegrafProvider` for connect it in your `main.ts` file. +If you want to configure a telegram bot webhook, you need to get a middleware via `getBotToken` helper in your `main.ts` file. To access it, you must use the `app.get()` method, followed by the provider reference: ```typescript -const telegrafProvider = app.get('TelegrafProvider'); +import { getBotToken } from 'nestjs-telegraf'; + +// ... +const bot = app.get(getBotToken()); ``` Now you can connect middleware: ```typescript -app.use(telegrafProvider.webhookCallback('/secret-path')); +app.use(bot.webhookCallback('/secret-path')); ``` The last step is to specify launchOptions in `forRoot` method: ```typescript TelegrafModule.forRootAsync({ - imports: [ConfigModule.forFeature(telegrafModuleConfig)], - useFactory: async (configService: ConfigService) => ({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ token: configService.get('TELEGRAM_BOT_TOKEN'), launchOptions: { webhook: { diff --git a/website/docs/middlewares.md b/website/docs/middlewares.md new file mode 100644 index 0000000..b16387a --- /dev/null +++ b/website/docs/middlewares.md @@ -0,0 +1,13 @@ +--- +id: middlewares +title: Middlewares +sidebar_label: Middlewares +slug: /middlewares +--- + +`nestjs-telegraf` has support of the Telegraf middleware packages. To use an existing middleware package, simply import it and add it to the middlewares array: +```typescript +TelegrafModule.forRoot({ + middlewares: [session()], +}), +``` diff --git a/website/docs/multiple-bots.md b/website/docs/multiple-bots.md new file mode 100644 index 0000000..7500a6f --- /dev/null +++ b/website/docs/multiple-bots.md @@ -0,0 +1,74 @@ +--- +id: multiple-bots +title: Multiple bots +sidebar_label: Multiple bots +slug: /multiple-bots +--- + +In some cases, you may need to run multiple bots at the same time. This can also be achieved with this module. To work with multiple bots, first create the bots. In this case, bot naming becomes mandatory. +```typescript +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TelegrafModule } from 'nestjs-telegraf'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TelegrafModule.forRootAsync({ + imports: [ConfigModule], + botName: 'cat', + useFactory: (configService: ConfigService) => ({ + token: configService.get('CAT_BOT_TOKEN'), + }), + inject: [ConfigService], + }), + TelegrafModule.forRootAsync({ + imports: [ConfigModule.forFeature(telegrafModuleConfig)], + botName: 'dog', + useFactory: async (configService: ConfigService) => ({ + token: configService.get('DOG_BOT_TOKEN'), + }), + inject: [ConfigService], + }), + ], +}) +export class AppModule {} +``` + +:::caution +Please note that you shouldn't have multiple bots without a name, or with the same name, otherwise they will get overridden. +::: + +You can also inject the `Bot` for a given bot: +```typescript +import { Injectable } from '@nestjs/common'; +import { InjectBot, Telegraf, Context } from 'nestjs-telegraf'; + +@Injectable() +export class EchoService { + constructor(@InjectBot('cat') private catBot: Telegraf) {} +} +``` +To inject a given `Bot` to a custom provider (for example, factory provider), use the `getBotToken()` function passing the name of the bot as an argument. +```typescript +{ + provide: CatsService, + useFactory: (catBot: Telegraf) => { + return new CatsService(catBot); + }, + inject: [getBotToken('cat')], +} +``` +Another useful feature of the `nestjs-telegraf` module is the ability to choose which modules should handle updates for each launched bot. By default, module searches for handlers throughout the whole app. To limit this scan to only a subset of modules, use the include property. + +```typescript +TelegrafModule.forRootAsync({ + imports: [ConfigModule], + botName: 'cat', + useFactory: (configService: ConfigService) => ({ + token: configService.get('CAT_BOT_TOKEN'), + include: [CatsModule], + }), + inject: [ConfigService], +}), +``` diff --git a/website/sidebars.js b/website/sidebars.js index 9912645..ea88714 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -6,6 +6,9 @@ module.exports = { 'telegraf-methods', 'bot-injection', 'async-configuration', + 'multiple-bots', + 'middlewares', + 'error-handling', ], Extras: ['extras/standalone-applications'], 'API Reference': ['api-reference/decorators'],