telegram-bot-lua
A feature-filled Telegram bot API library written in Lua, created by Matt. Supports Bot API 9.6 with full coverage of all available methods.
Installation
Requires Lua 5.1+ and LuaRocks:
luarocks install telegram-bot-lua
Quick Start
local api = require('telegram-bot-lua').configure('YOUR_BOT_TOKEN') function api.on_message(message) if message.text then api.send_message(message.chat.id, 'You said: ' .. message.text) end end api.run({ timeout = 60 })
api.run() is async by default: each update gets its own coroutine and all API calls are non-blocking.
Key Features
- Full Bot API 9.6 coverage (messages, media, payments, stickers, forums, games, gifts, stories, and more)
- Async-first architecture via copas: concurrent updates, parallel API calls, background tasks
- Built-in adapters: SQLite, PostgreSQL, Redis, OpenAI, Anthropic (Claude), and SMTP email
- Lua 5.1 - 5.5 support with automatic polyfills for bitwise operations and string.pack
- Clean opts-table pattern for all API methods
- Chainable keyboard and inline result builders
- Text formatting helpers for HTML, Markdown, and MarkdownV2
- Command parsing, pagination, deep links, and callback data encoding
- Member status helpers and chat permission checks
- Legacy v2 compatibility layer with deprecation warnings
Documentation
| Document | Description | |---|---| | Getting Started | Installation, configuration, and first bot | | Update Handlers | All available update handler functions | | API Methods | Complete method reference | | Builders | Keyboards, inline results, and type constructors | | Utilities | Formatting, command parsing, pagination, and tools | | Async / Concurrency | Concurrent updates, parallel calls, background tasks | | Adapters | Database, Redis, LLM, and email integrations | | Migration from v2 | Breaking changes and upgrade guide |
Example
local api = require('telegram-bot-lua').configure(os.getenv('BOT_TOKEN')) -- Connect adapters local db = api.db.connect({ driver = 'sqlite', path = 'bot.db' }) db:execute('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)') local llm = api.llm.new({ provider = 'anthropic', api_key = os.getenv('ANTHROPIC_API_KEY'), model = 'claude-sonnet-4-5-20250929', }) function api.on_message(message) if not message.text then return end local cmd = api.extract_command(message) if cmd and cmd.command == 'start' then local name = api.fmt.bold(api.get_name(message.from)) db:execute('INSERT OR IGNORE INTO users VALUES (?, ?)', {message.from.id, message.from.first_name}) api.send_message(message.chat.id, 'Welcome, ' .. name .. '!', { parse_mode = 'HTML', reply_markup = api.inline_keyboard() :row(api.row() :callback_data_button('Help', 'help') :callback_data_button('Ask AI', 'ai')) }) elseif cmd and cmd.command == 'ask' and cmd.args_str then api.send_typing(message.chat.id) local result = llm:chat({{ role = 'user', content = cmd.args_str }}) api.send_message(message.chat.id, result and result.content or 'Sorry, error occurred.') end end api.run({ timeout = 60 })
Module Structure
src/
main.lua -- Entry point, core HTTP, module loader
config.lua -- API endpoint configuration
polyfill.lua -- Lua 5.1+ compatibility (bit ops, string.pack)
async.lua -- Copas-based concurrency module
b64url.lua -- Base64 URL encoding/decoding
tools.lua -- Utility functions (formatting, file ops, etc.)
handlers.lua -- Update routing and on_* handler stubs (async-first)
builders.lua -- Keyboard, inline result, and type constructors
helpers.lua -- Member status check helpers
utils.lua -- Bot development utilities (fmt, commands, pagination)
compat.lua -- v2 backward compatibility layer
adapters/
init.lua -- Adapter registry and shared utilities
db.lua -- Database adapter (SQLite, PostgreSQL)
redis.lua -- Redis adapter (RESP protocol)
llm.lua -- LLM adapter (OpenAI, Anthropic)
email.lua -- Email adapter (SMTP)
methods/
messages.lua -- send_*, forward_*, copy_*, edit_*, delete_*
updates.lua -- get_updates, webhooks
chat.lua -- Chat management
members.lua -- Member management (ban, restrict, promote)
forum.lua -- Forum topic management
stickers.lua -- Sticker operations
inline.lua -- Inline queries and callback queries
payments.lua -- Invoices, payments, stars
games.lua -- Game methods
passport.lua -- Passport data errors
bot.lua -- Bot profile and settings
gifts.lua -- Gift methods
checklists.lua -- Checklist methods
stories.lua -- Story methods
suggested_posts.lua -- Suggested post methods
Testing
luarocks install busted
busted
Migrating from v2
v3 includes a compatibility layer that lets most v2 code run with deprecation warnings. Here's what to do:
1. Update
luarocks install telegram-bot-lua
LuaRocks handles dependency changes automatically (lpeg and html-entities removed, copas added).
2. Update your require (optional but recommended)
-- v2 local api = require('telegram-bot-lua.core').configure('TOKEN') -- v3 local api = require('telegram-bot-lua').configure('TOKEN')
The old require('telegram-bot-lua.core') still works but prints a deprecation warning.
3. Update method calls (optional but recommended)
v3 uses options tables instead of positional args. The compat layer auto-detects v2-style calls and converts them, but you should update your code:
-- v2 api.send_message(chat_id, text, nil, 'HTML', nil, nil, false, false, reply_params, reply_markup) api.send_photo(chat_id, photo, nil, 'Caption', 'HTML') api.answer_callback_query(id, 'Alert text', true) api.edit_message_text(chat_id, msg_id, text, 'HTML') api.run(1, 60) -- v3 api.send_message(chat_id, text, { parse_mode = 'HTML', reply_parameters = reply_params, reply_markup = reply_markup }) api.send_photo(chat_id, photo, { caption = 'Caption', parse_mode = 'HTML' }) api.answer_callback_query(id, { text = 'Alert text', show_alert = true }) api.edit_message_text(chat_id, msg_id, text, { parse_mode = 'HTML' }) api.run({ timeout = 60 })
4. Renamed methods
These v2 methods are aliased with deprecation warnings:
| v2 | v3 |
|----|-----|
| kick_chat_member(chat_id, user_id, until_date) | ban_chat_member(chat_id, user_id, opts) |
| get_chat_members_count(chat_id) | get_chat_member_count(chat_id) |
5. Async is now the default
api.run() uses copas for concurrent update processing. Each handler runs in its own coroutine. For the old sequential behaviour:
api.run({ sync = true, timeout = 60 })
What doesn't need migration
- Handler functions (
api.on_message,api.on_callback_query, etc.) — same pattern - Builder methods (
api.keyboard(),api.inline_keyboard(), etc.) — same API - Tool functions (tools.escape_html,
tools.comma_value, etc.) — same API - No config files, secrets, or environment variables to migrate
License
This project is licensed under the GPL-3.0 License - see the LICENSE file for details.
Copyright (c) 2017-2026 Matthew Hesketh