@effect-ak/tg-bot
v1.2.1
Published
Telegram Bot runner
Downloads
410
Readme
@effect-ak/tg-bot
Effect-based Telegram bot runner that handles long polling, update processing, and error management automatically.
Table of Contents
- Features
- Installation
- Quick Start
- Core Concepts
- Usage Examples
- Configuration
- API Reference
- How It Works
- Error Handling
- Playground
- Related Packages
- License
Features
- Effect-based: Built on top of Effect for powerful functional programming patterns
- Two Processing Modes: Handle updates one-by-one or in batches
- Automatic Long Polling: Manages connection to Telegram servers
- Type-Safe Handlers: Full TypeScript support for all update types
- Error Recovery: Configurable error handling strategies
- Concurrent Processing: Process multiple updates in parallel (up to 10 concurrent handlers)
- Hot Reload: Reload bot handlers without restarting
- Built-in Logging: Configurable logging levels
- No Public URL Required: Uses pull model - run bots anywhere, even in a browser
Installation
npm install @effect-ak/tg-bot effectpnpm add @effect-ak/tg-bot effectyarn add @effect-ak/tg-bot effectNote: effect is a peer dependency and must be installed separately.
Quick Start
import { runTgChatBot } from "@effect-ak/tg-bot"
runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "single",
on_message: [
{
match: ({ update }) => !!update.text,
handle: ({ update, ctx }) => ctx.reply(`You said: ${update.text}`)
}
]
})Core Concepts
Single Mode
In single mode, the bot processes each update individually with a dedicated handler for each update type.
Handler Format (v2 with guards):
on_message: [
{
match: ({ update, ctx }) => ctx.command === "/start", // optional filter
handle: ({ update, ctx }) => ctx.reply("Welcome!") // handler
},
{
match: ({ update }) => !!update.text,
handle: ({ ctx }) => ctx.reply("Got your message!")
},
{
handle: ({ ctx }) => ctx.ignore // fallback (no match = always runs)
}
]Context helpers:
ctx.reply(text, options?)- Send a messagectx.replyWithDocument(document, options?)- Send a documentctx.replyWithPhoto(photo, options?)- Send a photoctx.command- Parsed command (e.g., "/start", "/help")ctx.ignore- Skip response
Available Handlers:
on_message- New incoming messageon_edited_message- Message was editedon_channel_post- New channel poston_edited_channel_post- Channel post was editedon_inline_query- Inline queryon_chosen_inline_result- Chosen inline resulton_callback_query- Callback query from inline keyboardon_shipping_query- Shipping queryon_pre_checkout_query- Pre-checkout queryon_poll- Poll state updateon_poll_answer- User changed their answer in a pollon_my_chat_member- Bot's chat member status changedon_chat_member- Chat member status changedon_chat_join_request- Request to join chat
Legacy format (v1 - still supported):
on_message: (message) => {
if (!message.text) return BotResponse.ignore
return BotResponse.make({ type: "message", text: "Hello!" })
}Batch Mode
In batch mode, the bot receives all updates as an array and processes them together.
runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "batch",
on_batch: async (updates) => {
console.log(`Processing ${updates.length} updates`)
// Process updates...
return true // Continue polling
}
})Bot Response
Handlers return a BotResponse object that describes what to send back to the user.
Creating Responses:
import { BotResponse } from "@effect-ak/tg-bot"
// Send a message
BotResponse.make({
type: "message",
text: "Hello!"
})
// Send a photo
BotResponse.make({
type: "photo",
photo: {
file_content: photoBuffer,
file_name: "image.jpg"
},
caption: "Check this out!"
})
// Ignore update (don't send anything)
BotResponse.ignoreSupported Response Types:
All Telegram send_* methods are supported: message, photo, document, video, audio, voice, sticker, dice, etc.
Usage Examples
Echo Bot
import { runTgChatBot, defineBot } from "@effect-ak/tg-bot"
const ECHO_BOT = defineBot({
on_message: [
{
match: ({ update }) => !!update.text,
handle: ({ update, ctx }) => ctx.reply(update.text!)
}
]
})
runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "single",
...ECHO_BOT
})Command Handler
import { runTgChatBot } from "@effect-ak/tg-bot"
import { MESSAGE_EFFECTS } from "@effect-ak/tg-bot-client"
runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "single",
on_message: [
{
match: ({ ctx }) => ctx.command === "/start",
handle: ({ ctx }) => ctx.reply("Welcome! Send me any message.", {
message_effect_id: MESSAGE_EFFECTS["🎉"]
})
},
{
match: ({ ctx }) => ctx.command === "/help",
handle: ({ ctx }) => ctx.reply("Available commands:\n/start - Start bot\n/help - Show help")
}
]
})Batch Processing
import { runTgChatBot } from "@effect-ak/tg-bot"
import { makeTgBotClient } from "@effect-ak/tg-bot-client"
const client = makeTgBotClient({ bot_token: "YOUR_BOT_TOKEN" })
runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "batch",
poll: {
batch_size: 100,
poll_timeout: 60
},
on_batch: async (updates) => {
const messages = updates
.map(u => u.message)
.filter(m => m != null)
await client.execute("send_message", {
chat_id: "ADMIN_CHAT_ID",
text: `Processed ${messages.length} messages`
})
return true // Continue polling
}
})Using Effect
Advanced usage with Effect for composable async operations:
import { Effect, Micro, pipe } from "effect"
import { launchBot } from "@effect-ak/tg-bot"
Effect.gen(function* () {
const bot = yield* launchBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "single",
poll: {
log_level: "debug"
},
on_message: [
{
match: ({ update }) => !!update.text,
handle: async ({ ctx }) => {
await Effect.sleep("2 seconds").pipe(Effect.runPromise)
return ctx.reply("Delayed response!")
}
}
]
})
// Access bot fiber for control
yield* pipe(
Micro.fiberAwait(bot.fiber()!),
Effect.andThen(Effect.logInfo("Bot stopped")),
Effect.forkDaemon
)
}).pipe(Effect.runPromise)Hot Reload
import { runTgChatBot } from "@effect-ak/tg-bot"
const bot = await runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "single",
on_message: [
{
match: ({ update }) => !!update.text,
handle: ({ ctx }) => ctx.reply("Version 1")
}
]
})
// Later, reload with new handlers
setTimeout(() => {
bot.reload({
type: "single",
on_message: [
{
match: ({ update }) => !!update.text,
handle: ({ ctx }) => ctx.reply("Version 2 - Hot reloaded!")
}
]
})
}, 5000)Configuration
Poll Settings
Configure how the bot polls for updates:
runTgChatBot({
bot_token: "YOUR_BOT_TOKEN",
mode: "single", // or "batch"
poll: {
log_level: "debug", // "info" | "debug"
on_error: "continue", // "stop" | "continue"
batch_size: 50, // 10-100
poll_timeout: 30, // 2-120 seconds
max_empty_responses: 5 // Stop after N empty responses
},
on_message: [/* ... */] // handlers at top level
})Options:
log_level(default:"info"): Logging verbosity"info"- Basic logging (new messages, errors)"debug"- Detailed logging (all updates, responses)
on_error(default:"stop"): Error handling strategy"stop"- Stop bot on error"continue"- Continue polling after errors
batch_size(default:10): Number of updates to fetch per poll (10-100)poll_timeout(default:10): Long polling timeout in seconds (2-120)max_empty_responses(default:undefined): Stop after N consecutive empty responses (useful for testing)
API Reference
runTgChatBot(input)
Starts the bot with long polling.
Parameters:
bot_token(string, required): Bot token from @BotFathermode("single"|"batch", required): Processing modepoll(object, optional): Polling configurationon_message,on_callback_query, etc. (optional): Update handlers (for single mode)on_batch(required for batch mode): Batch handler function
Returns: Promise<BotInstance>
launchBot(input)
Launches bot and returns a bot instance for advanced control.
Returns: Micro<BotInstance>
BotInstance.reload(mode)- Hot reload handlersBotInstance.fiber()- Access underlying Effect fiber
defineBot(handlers)
Helper to define bot handlers with type checking and validation.
Parameters:
handlers(object): Handler functions for different update types
Returns: BotUpdatesHandlers
BotResponse.make(response)
Creates a bot response.
Parameters:
response(object): Response configuration withtypeand parameters
Returns: BotResponse
BotResponse.ignore
Singleton instance for ignoring updates (no response).
How It Works
Pull Model Architecture
The Telegram bot API supports both push and pull notification models. This package uses the pull model for several key advantages:
- Run bots anywhere without public URLs: No need to expose public ports or configure webhooks. You can run bots locally, in a browser, or behind firewalls.
- Leverage Telegram's infrastructure: Telegram stores updates for 24 hours, giving you plenty of time to process them.
- Simpler deployment: No SSL certificates, no webhook configuration, no reverse proxies required.
Architecture Diagram
graph TD
User[User] -->|Sends message| TgBot[Telegram Bot]
TgBot -->|Stores for 24h| Queue[Updates Queue<br/>api.telegram.org/bot/updates]
subgraph Your Code
Runner[Bot Runner<br/>@effect-ak/tg-bot]
Handler[Your Handler Function]
end
Runner -->|Long polling| Queue
Runner -->|Invokes with update| Handler
Handler -->|Returns BotResponse| Runner
Runner -->|Sends response| TgBotHow it works:
- User sends a message to your bot
- Telegram stores the update in a queue for 24 hours
- Bot runner polls the queue using long polling
- Runner invokes your handler function with the update
- Handler returns a
BotResponse - Runner sends the response back to Telegram
- Runner tracks the last processed update ID to avoid duplicates
Error Handling
The bot automatically handles errors at different levels:
Update Handler Errors
If a handler throws an error, the bot:
- Logs the error with update details
- Sends an error message to the user (in single mode)
- Continues processing other updates (if
on_error: "continue")
on_message: [
{
match: ({ ctx }) => ctx.command === "/error",
handle: () => {
throw new Error("Something went wrong!")
// Bot will catch this and send error message to user
}
},
{
match: ({ update }) => !!update.text,
handle: ({ ctx }) => ctx.reply("OK")
}
]Batch Handler Errors
In batch mode, returning false stops the bot:
on_batch: async (updates) => {
try {
// Process updates
return true // Continue
} catch (error) {
console.error(error)
return false // Stop bot
}
}Concurrent Processing
In single mode, up to 10 updates are processed concurrently. If some handlers fail, others continue processing.
Playground
Develop and test your bot directly in the browser:
No installation required - perfect for quick prototyping and learning!
Related Packages
This package is part of the tg-bot-client monorepo:
- @effect-ak/tg-bot-client - Type-safe HTTP client for Telegram Bot API
- @effect-ak/tg-bot-api - TypeScript types for Telegram Bot API and Mini Apps
- @effect-ak/tg-bot-codegen - Code generator that parses official documentation
License
MIT © Aleksandr Kondaurov
