telegram-menu-from-structure
v1.1.3
Published
There is a Telegram menu generation and processing module based on structured data
Downloads
332
Maintainers
Readme
Telegram Menu generated from Structure
About
A Telegram menu generation and processing module based on structured data.
Table of Contents
- Telegram Menu generated from Structure
- About
- Table of Contents
- Installation
- Key Features
- Key Components
- Usage
- Examples
- Testing
- Changelog
- Contributing
- License
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 objectmenuDefaults
- an object with default options for the menu, which contains the following fields:columnsMaxCount
- number, the maximum number of columns in the menurowsMaxCount
- number, the maximum number of rows in the menutextSummaryMaxLength
- number, the maximum length of the text summary of the menu itemspaceBetweenColumns
- number, the number of spaces between columnscmdPrefix
-/
, 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 submenulabel
- string, the label of the menutext
- string, the text of the menuid
- string, the id of the menu and the command that triggers the menu with adding/
at the beginningoptions
- object, the options of the menuContains 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 itemsetValue
- function, a function that sets the value of the menu itemremoveValue
- 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 anid
of the main submenu (item of astructure
object), where thedata
is an array of objects or object with the values of the subordinates items. See below in description ofstructure
object. For example if one of the main submenu (item of astructure
object) has a keyitem1
and it has a submenu with a keysubitem1
, the full path to thesubitem1
will beitem1.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 menurowsMaxCount
- number, the maximum number of rows in the menutextSummaryMaxLength
- number, the maximum length of the text summary of the menu itemspaceBetweenColumns
- 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 thestructure
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 thestructure
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 fieldsarray
- the submenu item is an list of objects with any amount of fields
label
- string, the label of the submenu itemsave
- function, a function that is called when the data of this submenu item is savedstructure
- description of the structure of the submenu item. It will have some differences forobject
andarray
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 fieldvalue
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 valuestring
- the field is a string valuenumber
- the field is a number valuearray
- 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 mandatoryoptional
- the field is optional, will shown if contains a value
editable
- boolean, if true, the field is editablesourceType
- string, the source type of the field. It can be one of the following values:list
- the field is a list of valuesinput
- 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 thelist
source type. Should return a Map object with the values of the listonSetBefore
- 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 setonSetAfter
- function, a function that is called after the value of the field is setonSetReset
- array, an array of fields that should be reset when the value of the field is changeddefault
- any, the default value of the fieldlabel
- string, the label of the fieldtext
- 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 buttoncommand
- 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 TelegrammessageText
- 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 TelegrammessageId
- the number identifier of the message(Menu) in TelegrammessageText
- 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 TelegrammessageId
- 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 TelegramuserId
- 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 Telegramcommand
- 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 itemisEvent
- boolean, if the command is an event from the menu button it has to betrue
, otherwise if it is a user input - should befalse
isTarge
- boolean, should be alwaysfalse
or skipped. It is used internally
Class constructor
Is used to create a new instance of the MenuItemRoot
class. It has only one parameter:
menuStructure
- the menu structure object that describes the menu. See details in Menu Structure Object
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 assignasync
send message function to synchronoussendMessage
parameter of themenuInitializationObject
and vice versa.sendMessage
orsendMessageAsync
- the function that sends the message(Menu) to Telegram. MandatoryeditMessage
oreditMessageAsync
- the function that edits the the message(Menu) in Telegram. MandatorydeleteMessage
ordeleteMessageAsync
- the function that deletes the message (user input and Menu itself). MandatorylogLevel
- the level of logging. Can be skipped. It can be one of the following values:error
- only errors are loggedwarning
- errors and warnings are loggedinfo
- errors and info messages are loggeddebug
- 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 messagewarn
- logs a warning messageinfo
- logs an info messagedebug
- 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 tonode: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
ornpm 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
ornpm 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.