npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

telegraf-721

v1.0.45

Published

Improvement to the telegraf library with functional programming, error handling, ...

Downloads

36

Readme

telegraf-721

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

  1. Functional Programming

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

  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"
      )
      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>>>
            
      ...