telegraf-721 v1.0.45
telegraf-721
Based on telegraf and telegraf-i18n
It has the following additions to improve the telegraf library usage:
Functional Programming
Either
Use left(l: L) or right(r: R) static functions in Either class to create Either for erroneous and valid values correspondingly.
export class HttpsError{ constructor(private message: string) { } get getError(){ return this.message } }
export class HttpsResponse{ constructor(public response: any) { } }
import axios from "axios"; import {Either} from "telegraf-721"; export class AxiosDatasource{ ... async get(): Promise<Either<RestResponseFailure, RestResponse>> { try{ const response = awiat this.axios.get(this.url) return Either.right(new HttpsResponse(response)) }catch(error){ return Either.left(new HttpsError(error.message)) } } }
The result of the above function(Either type) can be consumed using functions of the Either instance; fold, foldLeft, foldRight, getOrElse, getLeft or getRight.
import {AxiosDatasource} from "./axios_datasource" const someFunction = () => { const response = await new AxiosDatasource().get() response.fold(async l => { // Display or log error using l.getError }, async r => { // Consume valid value using r.response })
Option
You can use option similar to the Either
You can use Option.none() and Option.some() to create Option object.
Similar functions to consume the value of an Option is also present here.
Dependency Injection
You can register all your instances globally using the DependencyProvider class. You can register both singletons and lazy singletons. The provider class is a singleton on its own. It has a getInstance static function for instantiation, but you won't have to use it because it is instantiated internally. You just need to import that.
import {provider} from "telegraf-721" provider.registerSingleton( "unique indentifier", new AxiosDatasource() ) provider.registerLazySingleton( "unique indentifier", () => new AxiosDatasource() )
The first usage is better suited for instances that are more likely to be used or are necessary during instantiation. The second usage is better for instances that may not be needed during the lifecycle of the application or are not need during instantiation. You can fetch the instance using the get function.
import {provider} from "telegraf-721" const axiosDatasource = provider.get<AxiosDatasource>("unique indentifier")
You should specify the type of the instance you are fetching if you read values of the instance since it can't induce the type of the instance automatically.
Bot Helpers
So far, we have only added basic thing to write clean code. In this section we move to telegraf specific things.
MyCommand
This class is used to define a command in telegram. Simple usage with the dependency provider
import {provider, MyCommand} from "telegraf-721"; import {CommonCommandHandlers} from "../somewhere"; provider.registerLazySingleton( "startCommand", () => new MyCommand( "start", CommonCommandHandlers.start ) )
MyInlineKeyboard
This is a parent class to different classes that define an inline keyboard in telegram. This library has the following extensions:
MyUrlInlineKeyboard: An inline keyboard with url property set.
import {MyUrlInlineKeyboard} from "telegraf-721"; new MyUrlInlineKeyboard( "bot.urlbutton.label", "www.mywebsite.com" )
MyWebAppInlineKeyboard: An inline keyboard with web_app.url property set.
import {MyWebAppInlineKeyboard} from "telegraf-721"; new MyWebAppInlineKeyboard( "bot.webappbutton.label", "www.myminiapp.com" )
MyCallbackInlineKeyboard: Parent to the classes below
MyCoreCallbackInlineKeyboard: An inline keyboard with callback_data property set. The callback_data will be the coreCallback or a regExp with the coreCallback and the dataPattern you provide.
The regExp looks like ^${this.coreCallback}|${this.dataPattern}$
Usage with only a coreCallbackimport {MyCoreCallbackInlineKeyboard} from "telegraf-721"; import {InlineKeyboardHandlers} from "../somewhere"; new MyCoreCallbackInlineKeyboard( "bot.continueButton.label", "continue", InlineKeyboardHandlers.continue )
Usage with both coreCallback and dataPattern
import {MyCoreCallbackInlineKeyboard} from "telegraf-721"; import {InlineKeyboardHandlers} from "../somewhere"; import {itemNumberPattern} from "../somewhere"; new MyCoreCallbackInlineKeyboard( "bot.continueButton.label", "itemNumber", InlineKeyboardHandlers.onItemSelected, itemNumberPattern )
The above InlineKeyboard can be used to show a list of inline keyboards with different number values. See example under MyMarkup below.
MyDataPatternInlineKeyboard: An inline keyboard with callback_data property set. This one is similar to MyCoreCallbackInlineKeyboard except for the coreCallback
import {MyDataPatternInlineKeyboard} from "telegraf-721"; import {InlineKeyboardHandlers} from "../somewhere"; new MyDataPatternInlineKeyboard( "NOT DEFINED", '.+', InlineKeyboardHandlers.matchAny )
MyKeyboard
Just like MyInlineKeyboard, this is a parent class for the different forms of keyboards
- HandledKeyboard: Parent class for keyboards with handlers.
MyLabelPatternKeyboard: A keyboard for matching a range of values with regExp like the MyDataPatternInlineKeyboard.
import {MyLabelPatternKeyboard} from "telegraf-721"; import {KeyboardHandlers} from "../somewhere"; new MyLabelPatternKeyboard( ".+", KeyboardHandlers.matchAny )
MyLabelKeyboard: A keyboard only text set.
import {MyLabelPatternKeyboard} from "telegraf-721"; import {KeyboardHandlers} from "../somewhere"; new MyLabelKeyboard( "common.buttonLabels.back", KeyboardHandlers.back )
MyLabeledOnlyKeyboard: Similar to the above keyboard, but no handler function is attached. The result of tapping such button invokes a scene or bot handler function instead
MyExtraFunctionKeyboard: A keyboard with request_contact or request_location set.
import {MyExtraFunctionKeyboard} from "telegraf-721"; new MyExtraFunctionKeyboard( "user.buttonLabels.shareContact", { requestContact: true } )
MyMarkup
This class is used to create a reply_markup attribute for sendMessage and other functions that accept this attribute. It has four basic functions:
getInlineKeyboardMarkup: Used to create InlineKeyboardMarkup Previous Definition:
import {MyCoreCallbackInlineKeyboard} from "telegraf-721"; import {InlineKeyboardHandlers} from "../somewhere"; import {itemNumberPattern} from "../somewhere"; provider.registerLazySingleton( "numberedButton", new MyCoreCallbackInlineKeyboard( "bot.continueButton.label", "itemNumber", InlineKeyboardHandlers.onItemSelected, itemNumberPattern ) )
reply_markup creation
import {MyMarkup, MyCoreCallbackInlineKeyboard} from "telegraf-721"; const array = Array.from({ length: 8 }, (_, i) => i + 1); array.forEach(item => { keyboards.push( provider.get<MyCoreCallbackInlineKeyboard>(numberedButton).mutateAndGet({ localizationKey: item.toString(), data: item, createNewInstance: true, translated: true }) ) }) MyMarkup.getInlineKeyboardMarkup(ctx, keyboards)
This function takes a third parameter layout with type ButtonLayout
export interface ButtonLayout { countInRow?: number, actual?: number[] }
By default, countInRow is set to 2 actual is an array of numbers, where each number indicates the number of column in the nth(array index) row. It is given priority over countInRow if both are passed
getKeyboardMarkup: Used to create ReplyKeyboardMarkup
import {MyMarkup} from "telegraf-721"; MyMarkup.getKeyboardMarkup(ctx, [ provider.get("keyboard1"), provider.get("keyboard2"), ] )
This also has similar third parameter.
getRemoveKeyboardMarkup: Used to create ReplyKeyboardRemove
- getForceReplyMarkup: Used to create ForceReply
MyScene
A child class of the WizardScene of telegraf.
The code below shows the registration of a MyScene instance on the provider.import {provider, MyScene} from "telegraf-721"; import {UserRegistrationSceneHandlers} from "../somewhere"; provider.registerLazySingleton( "userRegistrationScene", () => new MyScene( "userRegistration", { enter: UserRegistrationSceneHandlers.enter, steps: [ UserRegistrationSceneHandlers.phoneNumber, UserRegistrationSceneHandlers.firstName, UserRegistrationSceneHandlers.lastName, UserRegistrationSceneHandlers.password, ], leave: UserRegistrationSceneHandlers.leave }, { keyboards: [ provider.get("keyboard1"), provider.get("keyboard2") ], inlineKeyboards: [ provider.get("registrationBackKeyboard"), provider.get("inlineKeyboard2") ], commands: [ provider.get("startCommand") ] } ) )
You can see that it takes three parameters:
- id: unique identifier of the scene
handlers: different functions that are invoked during the entry, stay and leaving of the scene.
You can move from one step of the scene to the next or back like so:export class UserRegistrationSceneHandlers{ static async phoneNumber(ctx: TelegrafContext) { // logic to check input if(logicPass) { return ctx.wizard.next() } } } export class UserRegistrationKeyboardHandlers{ static async back(ctx: TelegrafContext) { switch (ctx.wizard.cursor) { case 2: return ctx.wizard.back() } } } }
you can also use ctx.wizard.selectStep(5) to jump to a specific step.
interactors: different interactive elements you want to be invoked while you are in that scene.
You can collect your scenes in a stage and attach them to your bot.import {provider} from "telegraf-721" provider.registerSingleton("mainStage", new Scenes.Stage( [ provider.get("userRegistrationScene"), provider.get("anotherScene1"), provider.get("anotherScene2"), ] ))
MyBot
Contains the telegraf instance and other interactive elements that the telegraf instance should respond to
Typical usage:import {provider, MyBot} from "telegraf-721" const botConfig = provider.get<Config>("botConfig") const bot = new MyBot( "botToken", { middlewares: [ provider.get<Scenes.Stage<any>>("mainStage").middleware(), ], interactors: { keyboards: [ provider.get("botKeyboard1") ], inlineKeyboards: [ provider.get("botInlineKeyboard1") ], commands: [ provider.get("startCommand") ] }, translatorMiddleware: new I18n({ directory: path.join(process.cwd(), '/assets/locales'), defaultLanguage: 'en', sessionName: 'session', useSession: true, allowMissing: true, defaultLanguageOnMissing: true, }).middleware(), session: botConfig.redisUrl ? new RedisSession({ store: { url: botConfig.redisUrl!, host: '127.0.0.1', port: 6379, ...(botConfig.redisUrl?.startsWith('rediss') ? {tls: {}} : {}) }, getSessionKey: (ctx) => { if (!ctx.from || !ctx.chat) { return } return `MyBot|${ctx.from.id}:${ctx.chat.id}` } }) : session(), testEnv: botConfig.testEnv } );
The bots core function that makes a request to the api has been updated.
This was done to enforce a better error handling. The function looks like the following:async function mutateTelegrafAPICallFunction(tg: Telegram) { const oldCallApi: typeof tg.callApi = tg.callApi.bind(tg); tg.callApi = async function newCallApi(method, payload, signal) { return oldCallApi(method, payload, signal) .then((value) => { return Either.right(new Success(value)) }) .catch((e) => { console.log("Error caught in callApi\n", e) return Either.left(new SimpleFailure(e.message)) }) as any; }; }
As you can see from the function, any call to the telegram API responds with an Either.
This forces the consumer to handle both error and valid value cases, if the consumer plans to work with the API call response.
If not at least the error will be caught and logged to the console, preventing the whole application from crashing.
This is why the return type of all functions in the TelegrafContext has been altered from the original type.
Example:... reply(text: string, extra?: tt.ExtraReplyMessage): Promise<Either<Failure, Success<tt.Message>>> ...
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago