This documentation provides a clear and structured guide to building and managing bots using the custom Telegram bot framework. The framework focuses on flexibility, scalability, and adherence to best practices like the DRY (Don't Repeat Yourself) and SOLID principles.
The framework allows developers to easily create new features and integrate them seamlessly. To demonstrate its capabilities, an example feature, fruits, is included.
- Dynamic Command Registration: Features can be added to the framework by registering them in a single
featureRegistryobject. - Agnostic Input and Output Handling: Handlers for messages, commands, and button presses are decoupled from feature logic.
- Dynamic Main Menu: The main menu buttons are generated automatically based on registered features.
- Error Handling: Built-in mechanisms to catch and handle errors gracefully in commands, messages, and button presses.
- Extensibility: Adding new features is simple and does not require modifications to the core framework logic.
- Encapsulation: Features manage their own state independently, ensuring modularity and ease of debugging.
-
Input Handler (``) Handles commands, button presses, and messages agnostically. It prevents duplicate processing and ensures that each input is routed correctly to the respective feature.
-
Output Handler (``) Provides utilities for sending messages and buttons to users, abstracting Telegram Bot API specifics.
-
Feature Registry Maps commands to their respective feature start functions, enabling dynamic command handling and menu generation.
-
Main Bot File The central file (
bot.js) initializes the bot, registers handlers, and manages the main menu generation and/startcommand.
The fruits feature serves as an example to demonstrate how to implement a custom feature within the framework. It guides users through a flow of collecting five fruit names and then allows them to select their favorite from a dynamically generated menu.
let isStartHandled = false; // Tracks if /start has been handled
function handleCommand(bot, chatId, command) {
try {
console.log(`Command received: ${command}`);
if (command === '/start' && isStartHandled) {
console.log('Skipping /start as it has already been handled.');
return;
}
if (command === '/start') {
isStartHandled = true;
}
// Command-specific logic can go here
} catch (error) {
console.error('Error handling command:', error);
bot.sendMessage(chatId, 'An error occurred while processing your command.');
}
}
function handleButton(bot, chatId, data) {
try {
console.log(`Button pressed with data: ${data}`);
// Button-specific logic can go here
} catch (error) {
console.error('Error handling button press:', error);
bot.sendMessage(chatId, 'An error occurred while processing your choice.');
}
}
function handleMessage(bot, chatId, text) {
try {
console.log(`Message received: ${text}`);
// Message-specific logic can go here
} catch (error) {
console.error('Error handling message:', error);
bot.sendMessage(chatId, 'An error occurred while processing your message.');
}
}
module.exports = {
handleCommand,
handleButton,
handleMessage,
};function sendMessage(bot, chatId, text) {
try {
bot.sendMessage(chatId, text);
} catch (error) {
console.error('Error sending message:', error);
bot.sendMessage(chatId, 'An error occurred while sending a message.');
}
}
function sendButtons(bot, chatId, text, buttons) {
try {
bot.sendMessage(chatId, text, {
reply_markup: {
inline_keyboard: buttons,
},
});
} catch (error) {
console.error('Error sending buttons:', error);
bot.sendMessage(chatId, 'An error occurred while sending buttons.');
}
}
module.exports = {
sendMessage,
sendButtons,
};const input = require('../handlers/input');
const output = require('../handlers/output');
let fruits = [];
let activeFruitsFlow = {}; // Tracks active fruit collection flows by user
function start(bot, chatId) {
fruits = []; // Reset fruits on start
activeFruitsFlow[chatId] = true; // Mark this user as in the fruit collection flow
output.sendMessage(bot, chatId, 'Please type the name of a fruit. I need 5 in total.');
}
function handleFruitInput(bot, chatId, text) {
if (activeFruitsFlow[chatId] && fruits.length < 5) {
fruits.push(text);
const remaining = 5 - fruits.length;
if (remaining > 0) {
output.sendMessage(bot, chatId, `Got it! Please type another fruit (${remaining} more to go).`);
} else {
const buttons = fruits.map((fruit) => [
{ text: fruit, callback_data: fruit },
]);
output.sendButtons(bot, chatId, 'Thanks! Choose one of your favorite fruits:', buttons);
}
} else if (fruits.length >= 5) {
output.sendMessage(bot, chatId, 'You already provided 5 fruits. Please choose one from the buttons.');
} else {
output.sendMessage(bot, chatId, 'Something went wrong. Please try again.');
}
}
function handleButtonPress(bot, chatId, data) {
if (activeFruitsFlow[chatId]) {
output.sendMessage(bot, chatId, `You chose: ${data}`);
fruits = [];
activeFruitsFlow[chatId] = false; // End the fruit collection process
}
}
function isFruitCollectionActive(chatId) {
return activeFruitsFlow[chatId] === true;
}
module.exports = {
start,
handleFruitInput,
handleButtonPress,
isFruitCollectionActive,
};To add a new feature:
- Create a new file in the
featuresdirectory (e.g.,myFeature.js). - Implement the feature logic (e.g., handling messages, commands, or buttons).
- Register the feature in the
featureRegistryin the main bot file (bot.js). - Test the feature to ensure it integrates seamlessly with the framework.
This framework provides a robust and extensible foundation for building Telegram bots. The fruits feature demonstrates its capabilities, but you can easily extend it with custom features to suit your needs. By adhering to clean coding principles, this framework ensures maintainability and scalability for projects of any size.