Skip to content

ByteHolic/TelePlate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ€– MTCute Modular Bot Template

Telegram bot template built with mtcute, Bun, TypeScript, and Drizzle ORM. Features a NestJS-inspired modular architecture with dependency injection, decorators, and type safety.

πŸ’‘ Also check out TelePlate - Our grammY-based bot template!


✨ Features

  • πŸ—οΈ Modular Architecture - NestJS-style modules, services, and decorators
  • πŸ’‰ Dependency Injection - Custom DI container with automatic resolution
  • 🎨 Decorator-Based Handlers - Clean and intuitive update handling
  • πŸ—ƒοΈ Drizzle ORM - Type-safe database operations with SQLite
  • πŸ“ Path Aliases - Clean imports (@core, @common, @database, @modules)
  • ⚑ Bun Runtime - Lightning-fast performance
  • πŸ”§ Full TypeScript - End-to-end type safety
  • πŸ“Š Built-in Logger - Configurable logging system
  • βš™οΈ Environment Validation - Zod-based configuration validation
  • 🎭 30+ Event Types - Complete mtcute API coverage
  • πŸ”„ Hot Reload - Auto-restart in development mode

πŸ“¦ Usage

Follow these steps to set up and run your bot:

1. Create a New Repository

Start by creating a new repository using this template. Click here to create.

2. Environment Variables Setup

Create an environment variables file:

cp .env.example .env

Edit .env and set the required variables:

API_ID=12345678
API_HASH=your_api_hash
BOT_TOKEN=your_bot_token
NODE_ENV=development
LOG_LEVEL=debug
SESSION_NAME=bot_session
DATABASE_URL=./bot-data/bot.db

3. Database Setup

Initialize the database:

bun run db:push

4. Launching the Bot

Development Mode:

# Install dependencies
bun install

# Start bot with hot reload
bun run dev

Production Mode:

# Install production dependencies only
bun install --production

# Set NODE_ENV to production in .env
# Then start the bot
bun run start

πŸ“ List of Available Commands

  • bun run dev β€” Start in development mode with hot reload
  • bun run start β€” Start in production mode
  • bun run db:push β€” Push database schema
  • bun run db:studio β€” Open Drizzle Studio (database GUI)
  • bun run db:generate β€” Generate migrations

πŸ—‚οΈ Directory Structure

project-root/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ core/                      # Core framework
β”‚   β”‚   β”œβ”€β”€ di/                    # Dependency injection
β”‚   β”‚   β”‚   β”œβ”€β”€ container.ts       # DI container
β”‚   β”‚   β”‚   └── metadata.ts        # Metadata keys
β”‚   β”‚   β”œβ”€β”€ decorators/            # All decorators
β”‚   β”‚   β”‚   β”œβ”€β”€ injectable.decorator.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ inject.decorator.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ module.decorator.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ update.decorators.ts     # 30+ event decorators
β”‚   β”‚   β”‚   β”œβ”€β”€ message.decorators.ts    # Message filters
β”‚   β”‚   β”‚   β”œβ”€β”€ callback.decorators.ts   # Callback queries
β”‚   β”‚   β”‚   β”œβ”€β”€ inline.decorators.ts     # Inline mode
β”‚   β”‚   β”‚   └── chat.decorators.ts       # Chat events
β”‚   β”‚   β”œβ”€β”€ interfaces/
β”‚   β”‚   β”‚   └── module.interface.ts
β”‚   β”‚   └── module-loader.ts       # Module loader
β”‚   β”œβ”€β”€ common/                    # Common utilities
β”‚   β”‚   β”œβ”€β”€ config/                # Configuration
β”‚   β”‚   β”‚   β”œβ”€β”€ env.schema.ts      # Zod validation
β”‚   β”‚   β”‚   β”œβ”€β”€ env.validator.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ env.service.ts
β”‚   β”‚   β”‚   └── config.module.ts
β”‚   β”‚   └── logger/                # Logging system
β”‚   β”‚       β”œβ”€β”€ logger.service.ts
β”‚   β”‚       β”œβ”€β”€ logger.interface.ts
β”‚   β”‚       └── logger.module.ts
β”‚   β”œβ”€β”€ database/                  # Database layer
β”‚   β”‚   β”œβ”€β”€ schema/                # Drizzle schemas
β”‚   β”‚   β”‚   β”œβ”€β”€ users.schema.ts
β”‚   β”‚   β”‚   └── chats.schema.ts
β”‚   β”‚   β”œβ”€β”€ db.service.ts
β”‚   β”‚   └── database.module.ts
β”‚   β”œβ”€β”€ modules/                   # Bot modules
β”‚   β”‚   β”œβ”€β”€ user/                  # User module
β”‚   β”‚   β”‚   β”œβ”€β”€ user.service.ts    # Business logic
β”‚   β”‚   β”‚   β”œβ”€β”€ user.updates.ts    # Update handlers
β”‚   β”‚   β”‚   └── user.module.ts     # Module definition
β”‚   β”‚   └── chat/                  # Chat module
β”‚   β”‚       β”œβ”€β”€ chat.service.ts
β”‚   β”‚       β”œβ”€β”€ chat.updates.ts
β”‚   β”‚       └── chat.module.ts
β”‚   β”œβ”€β”€ bot.module.ts              # Root module
β”‚   └── index.ts                   # Entry point
β”œβ”€β”€ drizzle/                       # Database migrations
β”œβ”€β”€ drizzle.config.ts              # Drizzle configuration
β”œβ”€β”€ tsconfig.json                  # TypeScript config with paths
β”œβ”€β”€ package.json
└── .env                           # Environment variables

πŸš€ Quick Start Example

Create a New Module

// src/modules/hello/hello.service.ts
import { Injectable, Inject } from '@core/decorators';
import { LOGGER } from '@common/logger/constants';
import type { ILogger } from '@common/logger/logger.interface';

@Injectable()
export class HelloService {
  constructor(@Inject(LOGGER) private readonly logger: ILogger) {}

  getGreeting(name: string): string {
    this.logger.log(`Generating greeting for ${name}`);
    return `Hello, ${name}! πŸ‘‹`;
  }
}
// src/modules/hello/hello.updates.ts
import { Inject } from '@core/decorators';
import { OnCommand, OnText } from '@core/decorators';
import { TELEGRAM_CLIENT } from '@core/module-loader';
import { HelloService } from './hello.service';
import type { TelegramClient, Message } from '@mtcute/bun';

export class HelloUpdates {
  constructor(
    @Inject(HelloService) private readonly helloService: HelloService,
    @Inject(TELEGRAM_CLIENT) private readonly client: TelegramClient
  ) {}

  @OnCommand('hello')
  async handleHello(msg: Message) {
    const greeting = this.helloService.getGreeting(msg.sender.firstName);
    await this.client.sendText(msg.chat.id, greeting);
  }

  @OnText(/hi|hey/i)
  async handleGreeting(msg: Message) {
    await this.client.sendText(msg.chat.id, 'πŸ‘‹ Hi there!');
  }
}
// src/modules/hello/hello.module.ts
import { Module } from '@core/decorators';
import { HelloService } from './hello.service';
import { HelloUpdates } from './hello.updates';
import { LoggerModule } from '@common/logger/logger.module';

@Module({
  imports: [LoggerModule],
  providers: [HelloService],
  updates: [HelloUpdates],
  exports: [HelloService]
})
export class HelloModule {}

Register in bot.module.ts:

import { HelloModule } from '@modules/hello/hello.module';

@Module({
  imports: [
    ConfigModule,
    LoggerModule,
    DatabaseModule,
    HelloModule, // Add here
  ]
})
export class BotModule {}

🎨 Available Decorators

Message Handlers

@OnCommand('start')                  // Single command
@OnCommand(['help', 'about'])        // Multiple commands
@OnText()                            // Any text message
@OnText('hello')                     // Text contains "hello"
@OnText(/pattern/i)                  // Regex pattern
@OnPhoto()                           // Photo messages
@OnVideo()                           // Video messages
@OnAudio()                           // Audio messages
@OnVoice()                           // Voice messages
@OnDocument()                        // Document messages
@OnSticker()                         // Stickers
@OnAnimation()                       // GIFs
@OnContact()                         // Contacts
@OnLocation()                        // Location
@OnPoll()                            // Polls
@OnDice()                            // Dice

Update Handlers

@OnNewMessage()                      // New message
@OnEditMessage()                     // Message edited
@OnDeleteMessage()                   // Message deleted
@OnMessageGroup()                    // Album/media group
@OnChatMemberUpdate()                // Member status changed
@OnUserStatusUpdate()                // User online/offline
@OnUserTyping()                      // User typing
@OnHistoryRead()                     // Messages read
@OnBotStopped()                      // Bot blocked by user
@OnPollUpdate()                      // Poll updated
@OnPollVote()                        // Poll vote
@OnStoryUpdate()                     // Story posted
@OnBotReactionUpdate()               // Reaction added

Callback & Inline

@OnCallback()                        // Any callback query
@OnCallback('button_id')             // Specific callback data
@OnCallback(/^action_/)              // Regex pattern
@OnInline()                          // Any inline query
@OnInline('search')                  // Contains text
@OnChosenInline()                    // Inline result chosen

Chat Events

@OnNewChatMembers()                  // New members
@OnLeftChatMember()                  // Member left
@OnPinnedMessage()                   // Message pinned
@OnNewChatTitle()                    // Title changed
@OnNewChatPhoto()                    // Photo changed

πŸ—ƒοΈ Database Operations

Define Schema

// src/database/schema/posts.schema.ts
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content').notNull(),
  userId: integer('user_id').notNull(),
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});

export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;

Use in Service

import { eq } from 'drizzle-orm';

@Injectable()
export class PostService {
  constructor(@Inject(DbService) private readonly db: DbService) {}

  async createPost(data: NewPost) {
    return await this.db.db.insert(posts).values(data).returning();
  }

  async getPost(id: number) {
    return await this.db.db.query.posts.findFirst({
      where: eq(posts.id, id),
    });
  }
}

πŸ”§ Configuration

All configuration is done via environment variables validated with Zod:

// src/common/config/env.schema.ts
export const envSchema = z.object({
  API_ID: z.coerce.number().positive(),
  API_HASH: z.string().min(1),
  BOT_TOKEN: z.string().min(1),
  // Add your custom variables
  MY_VAR: z.string().default('default_value'),
});

Access in services:

constructor(@Inject(ENV_SERVICE) private readonly env: EnvService) {
  const apiId = this.env.apiId;
  const custom = this.env.get('MY_VAR');
}

🚒 Deployment

Using Docker

FROM oven/bun:latest

WORKDIR /app

COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production

COPY . .

RUN bun run db:push

CMD ["bun", "run", "start"]

Build and run:

docker build -t telegram-bot .
docker run -d --env-file .env telegram-bot

Using PM2

# Install PM2
bun add -g pm2

# Start
pm2 start bun --name "telegram-bot" -- run start

# Monitor
pm2 logs telegram-bot
pm2 monit

πŸ’‘ Best Practices

  1. Keep modules focused - One responsibility per module
  2. Use dependency injection - Better testability and maintainability
  3. Leverage path aliases - Keep imports clean (@core, @common, etc.)
  4. Log important events - Use the logger service
  5. Validate all inputs - Use Zod schemas
  6. Handle errors gracefully - Wrap handlers in try-catch
  7. Type everything - Take advantage of TypeScript

πŸ“š Learn More


🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing)
  5. Open a Pull Request

πŸ“„ License

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


πŸ™ Acknowledgments


πŸ“ž Support

If you like this project, please consider giving it a ⭐️ on GitHub!

Made with ❀️ by ByteHolic

About

Telegram Bot Template Based On MTCute

Topics

Resources

Stars

Watchers

Forks