telegraf-721
TypeScript icon, indicating that this package has built-in type declarations

1.0.38 • Public • Published

telegraf-721

Based on telegraf and telegraf-i18n
It has the following additions to improve the telegraf library usage:

  1. Functional Programming

    1. Either

      Can be used by extending Left or Right to create your corresponding error or valid value variants.

      import {Left} from "telegraf-721";
      
      export class HttpsError extends Left{
        constructor(private message: string) {
            super();
        }
      
        get getError(){
            return this.message
        }
      }  
      
      import {Right} from "telegraf-721";
      
      export class HttpsResponse extends Right{
        constructor(public response: any) {
            super();
        }
      }  
      

      You can use left(l: L) or right(r: R) static functions in Either class to create Either for erroneous and valid values correspondingly.

      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
         })        
      
    2. Option

      You can use option similar to the Either, except that you don't have to extend any parent class.
      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.

  2. 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.

  3. Bot Helpers

    So far, we have only added basic thing to write clean code. In this section we move to telegraf specific things.

    1. 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
        )
      )
      
    2. MyInlineKeyboard

      This is a parent class to different classes that define an inline keyboard in telegram. This library has the following extensions:
      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 coreCallback
      import {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
      )
      
    3. 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
         }
      )
      
    4. 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
    5. 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"),
          ]
        ))
        
    6. 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>>>
      
      ...
      

Readme

Keywords

Package Sidebar

Install

npm i telegraf-721

Weekly Downloads

2,128

Version

1.0.38

License

ISC

Unpacked Size

345 kB

Total Files

6

Last publish

Collaborators

  • mikiyas721