TeleBuilder is a simple Telegram bot framework for building command message-centric Telegram bots in TypeScript/JavaScript. It's built on top of GramJS, allowing you to create bots and userbots with more features and fewer limitations than using frameworks based on Telegram Bot API.
Note: TeleBuilder is still in early development and is primarily used for personal bot projects. Please report any bugs or suggest improvements.
- Command-Centric Design: Define and manage bot commands easily with a clear and concise interface.
- Command Locking: Prevent a command from running while another command is in progress.
- Easily Configurable Command Scopes: Limit command execution to specific users, chats, or peers.
- Command Config Caching: Store current command configuration on disk for updates and restarts.
- Lightweight configuration management: Load and manage configuration settings for your bot like node-config.
npm install telebuilder
Example of a bot built with TeleBuilder: jekyll-post-bot
TeleBuilder uses a ConfigService
to manage and load configuration settings for your bot. The service loads configuration files based on your environment and merges them to create a final, read-only configuration object that your application can use.
The configuration files are located in the config
directory of your project. By default, the service looks for a default.js
configuration file, and if an environment variable NODE_ENV
is set, it will also look for a configuration file named after the environment (e.g., development.js
, production.js
).
Here is an example of what your default configuration file might look like:
// config/default.js
import { readFile } from 'node:fs/promises';
// App version
const version = JSON.parse(await readFile(new URL('../package.json', import.meta.url))).version;
export default {
botConfig: {
botDataDir: process.env.TG_BOT_DATA_DIR || './botData',
apiId: Number.parseInt(process.env.TG_BOT_API_ID),
apiHash: process.env.TG_BOT_API_HASH,
token: process.env.TG_BOT_TOKEN,
appVersion: process.env.TG_BOT_APP_VERSION || version,
}
};
To access the configuration values in your application, use the config.get()
method. You can provide a key path as a string to access nested properties. For example:
import { config } from 'telebuilder/config';
const apiId = config.get<number>('botConfig.apiId');
const apiHash = config.get<string>('botConfig.apiHash');
const token = config.get<string>('botConfig.token');
For more details on setting up and managing configurations, refer to the configuration file in the project repository.
Entry module for your bot application. This is where you define your bot and its commands. Here’s a basic example of how to create and initialize your bot:
#!/usr/bin/env node
import { TelegramBotClient } from 'telebuilder';
import { StartCommand } from './commands/start.command.js';
const client = new TelegramBotClient({
commands: [
StartCommand,
]
});
await client.init();
Each command is a class that implements the Command
interface and is decorated with the @command
decorator.
There are required properties and methods that you need to implement in your command class:
-
command
: The command name that triggers the command. -
description
: A brief description of what the command does. -
scopes
: An array ofCommandScope
objects that define where the command and by whom it can be executed. -
langCodes
: An array of language codes for which the command is available. -
entryHandler
: The method that is called when the command is executed.
Here is an example of a command class:
@command
export class StartCommand implements Command {
command = 'start';
description = 'Start the bot';
scopes: CommandScope[] = [
{
name: 'Peer',
peer: channelAuthor
},
];
langCodes = [];
@handler({
event: {
fromUsers: [channelAuthor],
}
})
public async entryHandler(event: NewMessageEvent): Promise<void> {
if (!event.client || !event?.message?.senderId) return;
}
}
Event handlers are methods that are called when a specific event occurs. You can define event handlers for different types of events, such as new messages, edited messages, callback queries and other events supported by GramJS.
To define an event handler, use the @handler
decorator and provide the event type and any additional options that you want to apply to the handler. @handler
decorator isn't required for command entry handlers.
Example of an event handler:
@handler({
type: 'album',
event: {
chats: [channelId],
incoming: true,
outgoing: false,
},
validateCommandParams: false,
lock: false
})
@catchError()
public async getNewAlbum(event: AlbumEvent): Promise<void> {
// prevent edited messages from being processed
if (event.messages.length === 1 || !event?.client) return;
// process the album;
}
Commands can take parameters that are passed by the user when executing the command like /command param1=value1 param2=value2
.
You can define parameters for your commands using the @params
decorator. The @params
decorator takes an object that defines the parameters for the command. Each parameter is defined by a key-value pair where the key is the parameter name and the value is an object that defines the parameter schema. At the moment, the supported parameter types are string
, number
, boolean
, and enum
.
Here is an example of how to define parameters for a command:
@params params: CommandParamsSchema = {
param1: {
type: 'string',
required: true,
default: '',
},
param2: {
type: 'number',
required: false,
default: 0,
},
param3: {
type: 'enum',
enamValues: ['value1', 'value2', 'value3'],
required: false,
},
};
TeleBuilder allows you to define interactive button menus using the @buttonsReg decorator.
To create button menus within a command class:
-
Define Button Fields: Add class fields representing button layouts using
Buttons
type. Each button is typically created withButton.inline
, specifying the display text and callback data. -
Apply the
@buttonsReg
Decorator: Decorate these fields with@buttonsReg
. This decorator performs validation and preprocessing to ensure that each button's callback data is correctly linked to a handler method within the class.
The @buttonsReg
decorator performs the following actions:
-
Validation: It checks that each button's callback data corresponds to an existing callback query handler method in the class. If a matching handler is not found, it throws a
DecoratorException
. -
Data Prefixing: It prefixes the callback data of each button with the class name followed by a colon (
:
). This namespacing helps avoid conflicts between handlers in different classes. - Handler Linking: Ensures that the prefixed callback data matches the handler methods, facilitating correct event routing.
Here’s a brief example:
@command
export class SessionCommand implements Command {
@buttonsReg woSessionButtons: Buttons = [
[Button.inline('Create session', Buffer.from('createSession'))]
];
@handler({ type: HandlerTypes.CallbackQuery })
public async createSession(event: CallbackQueryEvent) {
// Handler implementation...
}
}
Full example of a command class with button: session.command.ts
-
Callback Data Consistency: The callback data specified in
Button.inline
(e.g.,'createSession'
) must exactly match the corresponding handler method name (createSession
) in the class. The@buttonsReg
decorator prefixes the callback data with the class name, so ensure handler methods are correctly named without the prefix. -
Decorator Responsibilities: The
@buttonsReg
decorator ensures that:- Every button has a corresponding handler method.
- Callback data is appropriately namespaced to prevent conflicts.
- Any mismatch between buttons and handlers results in a clear exception, aiding in debugging.
-
Error Handling: If a button's callback data does not match any handler method within the class, a
DecoratorException
is thrown, indicating the specific issue for easy resolution.
TeleBuilder provides a straightforward way to manage dependencies through the use of the @injectable
and @inject
decorators. This system ensures that services are automatically registered and resolved within the framework’s internal container.
-
@injectable
: Marks a class as a service that can be injected. -
@inject
: Injects the dependency into a class where it is needed.
Here’s an example of how to set this up:
@injectable
export class MyService {
public doSomething(): void {
console.log('Service is doing something.');
}
}
export class MyComponent {
@inject(MyService)
private readonly myService!: MyService;
public performAction(): void {
this.myService.doSomething();
}
}
TeleBuilder includes a @catchError
decorator to help manage errors in your event handlers gracefully. By using this decorator, you can define a custom error handler that will be invoked if an error occurs during the execution of the handler.
Example of error handling in a command handler:
@catchError(async (error) => {
// Custom error handling logic...
console.error('Custom Error:', error.message);
})
public async myMethod() {
// Method logic that might throw an error...
}
You can access the underlying TelegramClient
instance provided by GramJS using the @client
decorator or the getClient()
function from telebuilder/states
.
This is useful when you need to perform operations that require direct access to the Telegram client, such as sending messages, handling events, and interacting with the Telegram API.
@client
private readonly client!: TelegramClient;
//or
import { getClient } from 'telebuilder/states';
const client = getClient();
TeleBuilder is licensed under the MIT License. See LICENSE for more information.
If you have any suggestions or improvements, feel free to open an issue or submit a pull request.