Tools for developing bot for Telegram. Best used with Rails, but can be be used in standalone app. Supposed to be used in webhook-mode in production, and poller-mode in development, but you can use poller in production if you want.
Package contains:
- Ligthweight client for bot API (with fast and thread-safe httpclient under the hood).
- Controller with message parser. Allows to write separate methods for each command.
- Middleware and routes helpers for production env.
- Poller with automatic source-reloader for development env.
- Rake tasks to update webhook urls.
Here is sample telegram_bot_app with session, keyboards and inline queries. Run it on your local machine in 1 minute!
Add this line to your application's Gemfile:
gem 'telegram-bot'
And then execute:
$ bundle
Or install it yourself as:
$ gem install telegram-bot
Add telegram
section into secrets.yml
:
telegram:
bots:
# just set the token
chat: TOKEN_1
# or add username to support commands with mentions (/help@ChatBot)
auction:
token: TOKEN_2
username: ChatBot
# Single bot can be specified like this
bot: TOKEN
# or
bot:
token: TOKEN
username: SomeBot
From now clients will be accessible with Telegram.bots[:chat]
or Telegram.bots[:auction]
.
Single bot can be accessed with Telegram.bot
or Telegram.bots[:default]
.
You can create clients manually with Telegram::Bot::Client.new(token, username)
.
Username is optional and used only to parse commands with mentions.
There is request(path_suffix, body)
method to perform any query.
And there are also shortcuts for available queries in underscored style
(answer_inline_query
instead of answerInlineQuery
).
All this methods just post given params to specific URL.
bot.request(:getMe) or bot.get_me
bot.request(:getupdates, offset: 1) or bot.get_updates(offset: 1)
bot.send_message chat_id: chat_id, text: 'Test'
By default client will return parsed json responses. You can enable
response typecasting to virtus models using telegram-bot-types
gem:
# Add to your gemfile:
gem 'telegram-bot-types', '~> x.x.x'
# Enable typecasting:
Telegram::Bot::Client.typed_response!
# or for single instance:
bot.extend Telegram::Bot::Client::TypedResponse
bot.get_me.class # => Telegram::Bot::Types::User
Any API request error will raise Telegram::Bot::Error
with description in its message.
Special Telegram::Bot::StaleChat
is raised when bot can't post messages to the chat anymore.
class Telegram::WebhookController < Telegram::Bot::UpdatesController
# use callbacks like in any other controllers
around_action :with_locale
# Every update can have one of: message, inline_query, chosen_inline_result,
# callback_query.
# Define method with same name to respond to this updates.
def message(message)
# message can be also accessed via instance method
message == self.payload # true
# store_message(message['text'])
end
# This basic methods receives commonly used params:
#
# message(payload)
# inline_query(query, offset)
# chosen_inline_result(result_id, query)
# callback_query(data)
# Define public methods to respond to commands.
# Command arguments will be parsed and passed to the method.
# Be sure to use splat args and default values to not get errors when
# someone passed more or less arguments in the message.
#
# For some commands like /message or /123 method names should start with
# `on_` to avoid conflicts.
def start(data = nil, *)
# do_smth_with(data)
# There are `chat` & `from` shortcut methods.
response = from ? "Hello #{from['username']}!" : 'Hi there!'
# There is `respond_with` helper to set `chat_id` from received message:
respond_with :message, text: response
# `reply_with` also sets `reply_to_message_id`:
reply_with :photo, photo: File.open('party.jpg')
end
private
def with_locale(&block)
I18n.with_locale(locale_for_update, &block)
end
def locale_for_update
if from
# locale for user
elsif chat
# locale for chat
end
end
end
You can enable typecasting of update
with telegram-bot-types
by including
Telegram::Bot::UpdatesPoller::TypedUpdate
:
class Telegram::WebhookController < Telegram::Bot::UpdatesController
include Telegram::Bot::UpdatesController::TypedUpdate
def message(message)
message.class # => Telegram::Bot::Types::Message
end
end
There is support for sessions using ActiveSupport::Cache
stores.
# configure store in env files:
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}
class Telegram::WebhookController < Telegram::Bot::UpdatesController
include Telegram::Bot::UpdatesController::Session
# or just shortcut:
use_session!
# You can override global config
self.session_store = :file_store
def write(text = nil, *)
session[:text] = text
end
def read
respond_with :message, text: session[:text]
end
private
# By default it uses bot's username and user's id as a session key.
# Chat's id is used only when `from` field is empty.
# Override `session_key` method to change this behavior.
def session_key
# In this case session will persist for user only in specific chat:
"#{bot.username}:#{chat['id']}:#{from['id']}"
end
end
It's usual to support chain of messages like BotFather: after receiving command
it asks you for additional argument. There is MessageContext
for this:
class Telegram::WebhookController < Telegram::Bot::UpdatesController
include Telegram::Bot::UpdatesController::MessageContext
def rename(*)
# set context for the next message
save_context :rename
respond_with :message, text: 'What name do you like?'
end
# register context handlers to handle this context
context_handler :rename do |*words|
update_name words[0]
respond_with :message, text: 'Renamed!'
end
# You can do it in other way:
def rename(name = nil, *)
if name
update_name name
respond_with :message, text: 'Renamed!'
else
save_context :rename
respond_with :message, text: 'What name do you like?'
end
end
# This will call #rename like if it is called with message '/rename %text%'
context_handler :rename
# If you have a lot of such methods you can use
context_to_action!
# It'll use context value as action name for all contexts which miss handlers.
end
You can use CallbackQueyContext
in the similar way to split #callback_query
into
several specific methods. It doesn't require session support, and takes context from
data. If data has a prefix with colon like this my_ctx:smth...
it'll call
my_ctx_callback_query('smth...')
when there is such action method. Otherwise
it'll call callback_query('my_ctx:smth...')
as usual.
To process update run:
ControllerClass.dispatch(bot, update)
There is also ability to run action without update:
# Most likely you'll want to pass :from and :chat
controller = ControllerClass.new(bot, from: telegram_user, chat: telegram_chat)
controller.process(:help, *args)
Use telegram_webhooks
helper to add routes. It will create routes for bots
at "telegram/#{bot.token}" path.
# Create routes for all Telegram.bots to use same controller:
telegram_webhooks TelegramController
# Or pass custom bots usin any of supported config options:
telegram_webhooks TelegramController,
bot,
{token: token, username: username},
other_bot_token
# Use different controllers for each bot:
telegram_webhooks bot => TelegramChatController,
other_bot => TelegramAuctionController
# telegram_webhooks creates named routes.
# Route name depends on `Telegram.bots`.
# When there is single bot it will use 'telegram_webhook'.
# When there are it will use bot's key in the `Telegram.bots` as prefix
# (eg. `chat_telegram_webhook`).
# You can override this options or specify others:
telegram_webhooks TelegramController, as: :my_webhook
telegram_webhooks bot => [TelegramChatController, as: :chat_webhook],
other_bot => TelegramAuctionController,
admin_chat: TelegramAdminChatController
For Rack applications you can also use Telegram::Bot::Middleware
or just
call .dispatch(bot, update)
on controller.
Use rake telegram:bot:poller
to run poller. It'll automatically load
changes without restart in development env. Optionally specify bot to run poller for
with BOT
envvar (BOT=chat
).
This task will not work if you don't use telegram_webhooks
.
You can run poller manually with
Telegram::Bot::UpdatesPoller.start(bot, controller_class)
.
There is Telegram::Bot::ClientStub
class to stub client for tests.
Instead of performing API requests it stores them in requests
hash.
To stub all possible clients use Telegram::Bot::ClientStub.stub_all!
before
initializing clients. Most likely you'll want something like this:
RSpec.configure do |config|
# ...
Telegram.reset_bots
Telegram::Bot::ClientStub.stub_all!
config.after { Telegram.bot.reset }
# ...
end
There are also some helpers for controller tests.
Check out telegram/bot/updates_controller/rspec_helpers
and
telegram/bot/updates_controller/testing
.
Use rake telegram:bot:set_webhook
to update webhook url for all configured bots.
Certificate can be specified with CERT=path/to/cert
.
Initialize with bot = Telegram::Bot::Client.new(token, botan: 'botan token')
or just add botan
key in secrets.yml
:
telegram:
bot:
token: bot_token
botan: botan_token
Access to Botan client with bot.botan
.
Use bot.botan.track(event, uid, payload)
to track events.
There are some helpers for controllers in Telegram::Bot::UpdatesController::Botan
:
class Telegram::WebhookController < Telegram::Bot::UpdatesController
include Telegram::Bot::UpdatesController::Botan
# This will track with event: action_name & data: payload
before_action :botan_track_action
def smth(*)
# This will track event for current user only when botan is configured.
botan_track :my_event, custom_data
# or get access directly to botan client:
botan.track(...)
end
end
There is no stubbing for botan clients, so don't set botan token in tests.
After checking out the repo, run bin/setup
to install dependencies.
Then, run rake spec
to run the tests.
You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
.
To release a new version, update the version number in version.rb
,
and then run bundle exec rake release
, which will create a git tag for the version,
push git commits and tags, and push the .gem
file to rubygems.org.
To setup development for specific major Rails version use:
RAILS=5 bundle install
# or
RAILS=5 bundle update
Bug reports and pull requests are welcome on GitHub at https://github.com/telegram-bot-rb/telegram-bot.