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

telegram-menu-from-structure

v1.1.3

Published

There is a Telegram menu generation and processing module based on structured data

Downloads

332

Readme

Telegram Menu generated from Structure

NPM Version NPM Downloads GitHub license GitHub last commit GitHub issues GitHub pull requests

About

A Telegram menu generation and processing module based on structured data.

Table of Contents

Installation

To install the package, run:

npm install telegram-menu-from-structure

Key Features

  • Generates a Telegram menu from a structured object
  • Each submenu can be described as an object with any amount of fields or array of objects
  • If a submenu is an array, it automatically generates a list of items and allows the user to modify, add or delete items
  • Values of the menu items can be boolean, string, number, or array
  • For boolean values, the menu item is a button that toggles the value
  • For string values, the menu item is a button that allows the user to change the value via sending message to bot
  • For number values it is possible to set a min, max and step values
  • For array values, the menu item is a button that allows the user to add, delete or modify items in the array
  • Items of the arrays can be a boolean, string, number, or object with any amount of fields
  • So you can create a complex structure of menus with any amount of submenus and items
  • Currently the deep of the structure is limited by maximum size of "command" in Telegram API (64 characters)

Key Components

Exported Items

In current version the module exports the following items:

  • MenuItemRoot - a class that generates a menu from a structured object
  • menuDefaults - an object with default options for the menu, which contains the following fields:
    • columnsMaxCount - number, the maximum number of columns in the menu
    • rowsMaxCount - number, the maximum number of rows in the menu
    • textSummaryMaxLength - number, the maximum length of the text summary of the menu item
    • spaceBetweenColumns - number, the number of spaces between columns
    • cmdPrefix - /, the prefix of the command that triggers the menu

Menu Structure Object

The menu structure object is a JSON object that describes the menu. It has the following fields:

const menuStructure = {
  isRoot: true,
  label: 'Main Menu',
  text: 'This is the main menu',
  id: 'start',
  options: {
    getValue: (key, type) => data[key],
    setValue: (key, value, type) => (data[key] = value),
    removeValue: (key) => delete data[key],
    ...menuDefaults,
  },
  structure: {
  },
};

Header Fields of the Menu Structure Object

  • isRoot - boolean, if true, the menu is a root menu, otherwise it is a submenu

  • label - string, the label of the menu

  • text - string, the text of the menu

  • id - string, the id of the menu and the command that triggers the menu with adding / at the beginning

  • options - object, the options of the menu

    Contains a really important options - "links" to the functions which will manage a data behind the Menu.

    • getValue - function, a function that returns the value of the menu item
    • setValue - function, a function that sets the value of the menu item
    • removeValue - function, a function that removes the value of the menu item

    When the user changes the value of the menu item, the corresponding function is called. By default, the key of the data is an id of the main submenu (item of a structure object), where the data is an array of objects or object with the values of the subordinates items. See below in description of structure object. For example if one of the main submenu (item of a structure object) has a key item1 and it has a submenu with a key subitem1, the full path to the subitem1 will be item1.subitem1. If these functions are not provided, the menu will not be able to show and change the values of the menu items.

    The next options are the options for the menu drawing and processing:

    • columnsMaxCount - number, the maximum number of columns in the menu
    • rowsMaxCount - number, the maximum number of rows in the menu
    • textSummaryMaxLength - number, the maximum length of the text summary of the menu item
    • spaceBetweenColumns - number, the number of spaces between columns

    You can or not define it or use the default values from menuDefaults. As it presented in the example above.

Structure Field of the Menu Structure Object

This field is an object that describes the structure of the menu. Each key of the object is a submenu item. The value of the key is an object that describes the submenu item.

There is two examples of the submenu item:

  • Object with two fields with data

    ...
      structure: {
        configuration: {
          type: 'object',
          label: 'Configuration',
          save: () => logMenu('Saving configuration. Data:', JSON.stringify(data)),
          structure: {
            itemContent: {
              language: {
                type: 'string',
                presence: 'mandatory',
                editable: true,
                sourceType: 'list',
                source: getLanguages,
                onSetAfter: onLanguageChange,
                default: 'en',
                label: 'Menu language',
                text: 'Language of the Menu',
              },
              buttonsMaxCount: {
                type: 'number',
                subType: 'integer',
                options: {
                  min: menuDefaults.buttonsMaxCount.min,
                  max: menuDefaults.buttonsMaxCount.max,
                  step: menuDefaults.buttonsMaxCount.step,
                },
                sourceType: 'input',
                presence: 'mandatory',
                editable: true,
                onSetAfter: onButtonMaxCountChange,
                default: menuDefaults.buttonsMaxCount.default,
                label: 'Max buttons on "page"',
                text: 'Max count of buttons on the one "page" of the menu',
              },
            },
          },
        },
      }

    In this example the configuration is a key of the structure object. It's a key which will be used to store a data object of the submenu.

  • Array of objects with several fields with data

    ...
      structure: {
        items: {
          type: 'array',
          label: 'Items',
          save: () => logMenu('Saving items. Data:', JSON.stringify(data)),
          structure: {
            primaryId: (data, isShort = false) => `${data.label} ${data.enabled ? '✅' : '❌'}`,
            label: 'Item',
            text: 'Item for example',
            itemContent: {
              label: {
                type: 'string',
                presence: 'mandatory',
                editable: true,
                sourceType: 'input',
                label: 'Label',
                text: 'Item identification label',
              },
              enabled: {
                type: 'boolean',
                presence: 'mandatory',
                editable: true,
                default: false,
                onSetBefore: (currentItem, key, data, path) => {
                  logMenu(
                    `onSetBefore: currentItem: ${JSON.stringify(currentItem)}, key: ${key}, data: ${JSON.stringify(
                      data,
                    )}, path: ${path}`,
                  );
                  if (data.label !== undefined && data.type !== undefined || currentItem[key] === true) {
                    return true;
                  } else {
                    logMenu('Item is not ready for enabling');
                    return false;
                  }
                },
                label: 'Enabled',
                text: 'Enable/disable item',
              },
              type: {
                type: 'string',
                presence: 'mandatory',
                editable: true,
                sourceType: 'list',
                source: sourceTypes,
                onSetReset: ['enabled'],
                label: 'Type of source',
                text: 'Type of source, i.e. chat/group/channel',
              },
            },
          },
        },
      },

    In this example the items is a key of the structure object. It's a key which will be used to store a data array of the submenu.

Fields of the Submenu Item Object
  • type - string, the type of the submenu item. On this level it can be one of the following values:
    • object - the submenu item is an object with any amount of fields
    • array - the submenu item is an list of objects with any amount of fields
  • label - string, the label of the submenu item
  • save - function, a function that is called when the data of this submenu item is saved
  • structure - description of the structure of the submenu item. It will have some differences for object and array types.
Fields of the Submenu Item structure Object for object type

There is only one field for object type:

  • itemContent - object, the structure of the submenu item. Each key of the object is a field of the submenu item. The value of the key is an object that describes the field of the submenu item.
Fields of the Submenu Item structure Object for array type

There is several additional fields for array type:

  • primaryId - string with Id of any field of the submenu item. Or function which will return the Id based on the data of the submenu item.
  • label - string, the label of the submenu item These two above fields are used to generate header of the submenu item.
  • text - string with the text of the submenu item header. Or function which will return the text based on the data of the submenu item.
  • plain - boolean, if true, the submenu items will be shown as single buttons, otherwise they will be shown as a list of buttons. There is some specifics of "plain" structures - they are is objects too, but with one predefined field value which is a value of the submenu item.
  • itemContent - object, the structure of the submenu item. Each key of the object is a field of the submenu item. The value of the key is an object that describes the field of the submenu item.
How the Items of Submenu are described in the itemContent object

The itemContent object has the following has an list of attributes, where the key is a field of the submenu item. The value of the key is an object that describes the field of the submenu item. This object can have the following fields:

  • type - string, the type of the field. It can be one of the following values:
    • boolean - the field is a boolean value
    • string - the field is a string value
    • number - the field is a number value
    • array - the field is an array of objects or values
  • presence - string, the presence of the field. Or function which will return the presence based on the data of the submenu item. It can be one of the following values:
    • mandatory - the field is mandatory
    • optional - the field is optional, will shown if contains a value
  • editable - boolean, if true, the field is editable
  • sourceType - string, the source type of the field. It can be one of the following values:
    • list - the field is a list of values
    • input - the field is an input field, i.e. the user can enter a value by sending a message to the bot on request
  • source - function, a function that returns the source of the field. It is used for the list source type. Should return a Map object with the values of the list
  • onSetBefore - function, a function that is called before the value of the field is set. It can be used to check the value of the field before it is set
  • onSetAfter - function, a function that is called after the value of the field is set
  • onSetReset - array, an array of fields that should be reset when the value of the field is changed
  • default - any, the default value of the field
  • label - string, the label of the field
  • text - string, the text of the field

External functions to interact with Telegram

There are four external function, dependant on the external library to work with Telegram, which should be provided to the MenuItemRoot class:

  • The first one is a function that generates a button for the menu, acceptable by external library to work with Telegram.
    • makeButton - it should has the following parameters:
      • label - the label of the button
      • command - the command that triggers the button And should return the button object acceptable by external library to work with Telegram
  • And three functions to interact with Telegram. These functions can be "usual", i.e. synchronous type or "async" type. It depends on the external library to work with Telegram. Please see details below
    • sendMessage - a function that sends the message(Menu) to Telegram It should has three parameters:
      • peerId - the unique identifier of the chat with the user in external library to work with Telegram
      • messageText - the text part of the message(Menu)
      • messageButtons - the array with a buttons part of the message(Menu) And should return the number identifier of the newly sended message(Menu) in Telegram
    • editMessage - a function that edits the the message(Menu) in Telegram It should has four parameters:
      • peerId - the unique identifier of the chat with the user in external library to work with Telegram
      • messageId - the number identifier of the message(Menu) in Telegram
      • messageText - the text part of the message(Menu)
      • messageButtons - the array with a buttons part of the message(Menu) And should return the true or false if the message was edited successfully
    • deleteMessage - a function that deletes the message (user input and Menu itself) It should has two parameters:
      • peerId - the unique identifier of the chat with the user in external library to work with Telegram
      • messageId - the number identifier of the message(Menu) in Telegram And should return the true or false if the message was deleted successfully

Receive and process user input

The main and only one method to receive and process user input is the method onCommand of the MenuItemRoot class. There is an async function which should be called on reaction of the external library to work with Telegram when the user sends a message to the bot or pressed a button in the menu. It currently has a lot of parameters:

  • peerId - the unique identifier of the chat with the user in external library to work with Telegram
  • userId - the unique identifier of the user in external library to work with Telegram. Used internally to separate the technical data of different users (like last message id, etc.)
  • messageId - the number identifier of the message(Menu) in Telegram
  • command - the command which was sent by the user. It can be a command from menu button or an user input to change the value of the menu item
  • isEvent - boolean, if the command is an event from the menu button it has to be true, otherwise if it is a user input - should be false
  • isTarge - boolean, should be always false or skipped. It is used internally

Class constructor

Is used to create a new instance of the MenuItemRoot class. It has only one parameter:

Menu initialization method

Is used to initialize the menu. It has only one parameter:

  • menuInitializationObject with several fields:
    • makeButton - the function that generates a button for the menu. Mandatory. The telegram interaction functions can be synchronous or asynchronous. It is critically important to assign this function to the appropriate type. Do not assign async send message function to synchronous sendMessage parameter of the menuInitializationObject and vice versa.
    • sendMessage or sendMessageAsync - the function that sends the message(Menu) to Telegram. Mandatory
    • editMessage or editMessageAsync - the function that edits the the message(Menu) in Telegram. Mandatory
    • deleteMessage or deleteMessageAsync - the function that deletes the message (user input and Menu itself). Mandatory
    • logLevel - the level of logging. Can be skipped. It can be one of the following values:
      • error - only errors are logged
      • warning - errors and warnings are logged
      • info - errors and info messages are logged
      • debug - errors, info messages and debug messages are logged
    • logger- the instance of any external logger class. Can be skipped. The logger should have the following methods:
      • error - logs an error message
      • warn - logs a warning message
      • info - logs an info message
      • debug - logs a debug message
    • i18n - the instance of any external i18n class. Can be skipped. The i18n should have the following methods:
      • __ - translates a text. Should has possibility to process formatted strings with parameters. Similar to node:util.format

Usage

New instance of the MenuItemRoot class

At first you should import the module and then create a new instance of the MenuItemRoot class with the menu structure object as a parameter:

import { MenuItemRoot, menuDefaults } from 'telegram-menu-from-structure';
...
const menu = new MenuItemRoot(menuStructure);

Initialize the menu

At second you should prepare the makeButton amd Telegram interaction functions and initialize the menu:

...
const makeButton = (label, command) => {
  // Generate the button for Telegram
};

const sendMessageAsync = async (peerId, messageText, messageButtons) => {
  // Send the message to Telegram
};
const editMessageAsync = async (peerId, messageId, messageText, messageButtons) => {
  // Edit the message in Telegram
};
const deleteMessageAsync = async (peerId, messageId) => {
  // Delete the message in Telegram
};

menu.init({
  makeButton: makeButton,
  sendMessageAsync: sendMessageAsync,
  editMessageAsync: editMessageAsync,
  deleteMessageAsync: deleteMessageAsync,
  logLevel: 'debug',
  },
});

Receive and process user input

There is an example to use the Menu with GramJs library:

import { MenuItemRoot, menuDefaults } from 'telegram-menu-from-structure';
import { TelegramClient } from 'telegram';
...
const client = new TelegramClient(
  ...
);

const allowedUsers = [
  ...
]; // List of allowed users
...
const menu = new MenuItemRoot(menuStructure);
...
menu.init(
  ...
);
...
client.addEventHandler((event) => {
  const {peer: peerId, msgId: messageId, data} = event.query;
  if (data !== undefined) {
    const command = data.toString();
    if (command.startsWith(menuDefaults.cmdPrefix)) {
      menu.onCommand(peerId, peerId.userId, messageId, command, true);
    }
  }
}, new CallbackQuery({chats: allowedUsers}));

client.addEventHandler((event) => {
  const {peerId, id: messageId, message: command} = event.message;
  menu.onCommand(peerId, peerId.userId, messageId, command, false);
}, new NewMessage({chats: allowedUsers}));

Examples

Simple console example aka Demo

This example is a simple console example that demonstrates how to module will work from the user point of view. It's emulates the Bot interaction with the user in the console.

You can test this module without real Telegram bot.

It is configured to provide two submenu items: Configuration and Items. The Configuration submenu item has two fields: language and buttonsMaxCount. The Items submenu item is an array of objects with three fields: label, enabled, and type.

You can go thru the menu, change the values of the fields, add, delete or modify the items of the array. The menu will show the changes in the console. At start it will have no data, but you can manage it as you want during the work with the menu. After exit the menu the data will not be saved.

To run the example, use the following command:

cd examples/simple-no-telegram-console-mode
npm install
npm start

By default this example demo will use the "external" version of Menu package. It will be downloaded from the npm repository by npm install command.

If you want to use the local version of the Menu package, you should use the following command, instead of npm start:

npm run start-local

But npm install should be run before this command to install other required packages.

As result you will see the menu in the console and you can interact with it. It will at start emulate the sending /start command to the bot and will show the result of the menu generation in the console:

Deleting message: 101 from user "test"
Sending message to user:
 text: This is the main menu
 buttons:
(  #0) Configuration
(  #1) Items []
(  #2) Exit
Enter button Id in format "#Number" or input data (if requested):

You can navigate thru the menu by input the number of the button with mandatory # symbol as prefix. Or you can input the data for the field of the submenu item, if it will be requested. Like this:

Message with id 201 to user is edited:
 text:
Please enter the "Max buttons on "page"" integer (min: 1, max: 100, step: 1) value:
 buttons:
(  #0) Cancel
Enter button Id in format "#Number" or input data (if requested):

Except the menu you will see the additional messages to explain the actions and results of the actions. Like this:

Deleting message: 101 from user "test"

or

Message with id 201 to user is edited:

or

Saving configuration. Data: {"buttonsOffset.test":0,"lastCommand.test":"/configuration?buttonsMaxCount","menuMessageId.test":201,"configuration":{"buttonsMaxCount":10}}

Take in account, you will work as "user" with id "test", as it is hardcoded in the example. User messages will start from 101. Bot messages will start from 201.

Notes:

  • The example is not a real Telegram bot. It is a console application that emulates the interaction with the bot.
  • If you receive errors on start, or try to use "local" mode or update an dependencies, please run npm install or npm update to install or update the required packages.

Simple Telegram bot example based on Telegraf library

This example is a simple Telegram bot example that demonstrates how to module will work with real Telegram bot. It's based on the Telegraf library.

You can test this module only if you have a real Telegram bot token.

Instructions how to get the token and create a bot can be found here.

To run the example, use the following command:

cd examples/simple-telegraf
npm install
npm start

By default this example demo will use the "external" version of Menu package. It will be downloaded from the npm repository by npm install command.

If you want to use the local version of the Menu package, you should use the following command, instead of npm start:

npm run start-local

But npm install should be run before this command to install other required packages.

You have to input the Telegram bot token on a script request. After that the bot will be started and you can interact with it in the Telegram chat.

As result you will see the menu in the Telegram chat with the bot and you can interact with it.

Additional logging will be shown in the console. You can see the what is happening with data and messages.

Notes:

  • If you receive errors on start, or try to use "local" mode or update an dependencies, please run npm install or npm update to install or update the required packages.

Testing

To run tests, use the following command:

npm test

The tests are written using Jest and can be found in the test directory.

Changelog

See the CHANGELOG

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

License

This project is licensed under the MIT License. See the LICENSE file for details.