Git Product home page Git Product logo

telegram-bot's Introduction

Telegram::Bot

Gem Version Code Climate Lint & Test

Tools for developing Telegram bots. Best used with Rails, but can 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:

  • Lightweight client for bot API (with fast and thread-safe httpclient under the hood).
  • Controller with message parser: define methods for commands, not case branches.
  • Middleware and routes helpers for production env.
  • Poller with automatic source-reloader for development env.
  • Rake tasks to update webhook urls.
  • Async mode. Let the queue adapter handle network errors!

Here is sample telegram_bot_app with session, keyboards and inline queries. Run it on your local machine in 1 minute!

And here is app template to generate new rails app in seconds.

See examples and cookbook in the wiki.

Table of Contents

Installation

Add this line to your application's Gemfile:

gem 'telegram-bot'

And then execute:

bundle

Or install it yourself as:

gem install telegram-bot

Require if necessary:

require 'telegram/bot'

Usage

Configuration

While clients can be instantiated explicitly, there is Telegram.bots_config= method to configure app-wide clients, which are accessible via Telegram.bots. It accepts hash of {bot_id: bot_config}, and there is special id :default which is used for Telegram.bot.

Telegram.bots_config = {
  default: DEFAULT_BOT_TOKEN,
  chat: {
    token: CHAT_BOT_TOKEN,
    username: 'ChatBot', # to support commands with mentions (/help@ChatBot)
    server: 'http://local.bot.api.server', # for Local Bot API Server
  },
}

Telegram.bot.get_updates
Telegram.bot == Telegram.bots[:default] # true
Telegram.bots[:chat].send_message(...)

Configuration in Rails app

In Rails app Telegram.bots_config is read from secrets.yml automatically from telegram section:

development:
  telegram:
    # Single bot can be specified like this
    bot: TOKEN
    # or
    bot:
      token: TOKEN
      username: SomeBot
      server: http://local.bot.api.server

    # For multiple bots in single app use hash of `internal_bot_id => settings`
    bots:
      # just set the bot token
      chat: TOKEN_1
      # or add username to support commands with mentions (/help@ChatBot)
      auction:
        token: TOKEN_2
        username: ChatBot

For Rails >= 5.2 Telegram::Bot searches for config first in credentials and then in secrets. To use credentials as config store, add telegram section to credentials instead of secrets using rails credentials:edit. In this case be aware of that Rails < 6.0 may not load credentials in dev environment by default. In Rails >= 6.0 run rails credentials:edit --environment development to configure bot in each environment.

I suggest not using Rails 5.2 credentials because it can lead to leakage of sensitive data and it's more difficult to use in multiple environments. See secure_credentials gem for better option.

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].

Client

Client is instantiated 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 shortcuts for all available requests in underscored style (answer_inline_query(params) instead of answerInlineQuery).

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')

There is no magic, they just pass params as is and set path_suffix. See list of available methods fetched from API docs. Please open PR or issue if it misses methods from new API versions.

Any API request error will raise Telegram::Bot::Error with description in its message. Special Telegram::Bot::Forbidden is raised when bot can't post messages to the chat anymore.

Typed responses

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

Controller

Controller makes it easy to keep bot's code readable. It does nothing more than finding out action name for update and invoking it. So there is almost no overhead comparing to large switch, while you can represent actions as separate methods keeping source much more readable and supportable.

New instance of controller is instantiated for each update. This way every update is processed in isolation from others.

Bot controllers like usual rails controllers provides features like callbacks, rescue_from and instrumentation.

class Telegram::WebhookController < Telegram::Bot::UpdatesController
  # use callbacks like in any other controller
  around_action :with_locale

  # Every update has one of: message, inline_query, chosen_inline_result,
  # callback_query, etc.
  # Define method with the same name to handle this type of update.
  def message(message)
    # store_message(message['text'])
  end

  # For the following types of updates commonly used params are passed as arguments,
  # full payload object is available with `payload` instance method.
  #
  #   message(payload)
  #   inline_query(query, offset)
  #   chosen_inline_result(result_id, query)
  #   callback_query(data)

  # Define public methods ending with `!` to handle 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.
  def start!(word = nil, *other_words)
    # do_smth_with(word)

    # full message object is also available via `payload` instance method:
    # process_raw_message(payload['text'])

    # There are `chat` & `from` shortcut methods.
    # For callback queries `chat` is taken from `message` when it's available.
    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

Reply helpers

There are helpers for basic responses. They just set chat/message/query identifiers from the update. See ReplyHelpers module for more information. Here are these methods signatures:

def respond_with(type, params); end
def reply_with(type, params); end
def answer_inline_query(results, params = {}); end
def answer_callback_query(text, params = {}); end
def edit_message(type, params = {}); end
def answer_pre_checkout_query(ok, params = {}); end
def answer_shipping_query(ok, params = {}); end

Optional typecasting

You can enable typecasting of update with telegram-bot-types by including Telegram::Bot::UpdatesController::TypedUpdate:

class Telegram::WebhookController < Telegram::Bot::UpdatesController
  include Telegram::Bot::UpdatesController::TypedUpdate

  def message(message)
    message.class # => Telegram::Bot::Types::Message
  end
end

Session

This API is very close to ActiveController's session API, but works different under the hood. Cookies can not be used to store session id or whole session (like CookieStore does). So it uses key-value store and session_key method to build identifier from update.

Store can be one of numerous ActiveSupport::Cache stores. While :file_store is suitable for development and single-server deployments without heavy load, it doesn't scale well. Key-value databases with persistance like Redis are more appropriate for production use.

# In rails app store can be configured in env files:
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}

# In other app it can be done for all controllers with:
Telegram::Bot::UpdatesController.session_store = :redis_store, {expires_in: 1.month}
# or for specific one:
OneOfUpdatesController.session_store = :redis_store, {expires_in: 1.month}

Default session id is made from bot's username and (from || chat)['id']. It means that session will be the same for updates from user in every chat, and different for every user in the same group chat. To change this behavior you can override session_key method, or even define multiple sessions in single controller. For details see Session module.

class Telegram::WebhookController < Telegram::Bot::UpdatesController
  include Telegram::Bot::UpdatesController::Session
  # or just shortcut:
  use_session!

  # You can override global config for this controller.
  self.session_store = :file_store

  def write!(text = nil, *)
    session[:text] = text
  end

  def read!(*)
    respond_with :message, text: session[:text]
  end

  private

  # In this case session will persist for user only in specific chat.
  # Same user in other chat will have different session.
  def session_key
    "#{bot.username}:#{chat['id']}:#{from['id']}" if chat && from
  end
end

Message context

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_from_message
    respond_with :message, text: 'What name do you like?'
  end

  # register context handlers to handle this context
  def rename_from_message(*words)
    update_name words[0]
    respond_with :message, text: 'Renamed!'
  end

  # You can use same action name as context name:
  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
end

Callback queries

You can include CallbackQueryContext module to split #callback_query into several 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 invokes my_ctx_callback_query('smth...') when such action method is defined. Otherwise it invokes callback_query('my_ctx:smth...') as usual. Callback queries without prefix stay untouched.

# This one handles `set_value:%{something}`.
def set_value_callback_query(new_value = nil, *)
  save_this(value)
  answer_callback_query('Saved!')
end

# And this one is for `make_cool:%{something}`
def make_cool_callback_query(thing = nil, *)
  do_it(thing)
  answer_callback_query("#{thing} is cool now! Like a callback query context.")
end

Routes in Rails app

There is telegram_webhook helper for rails app to define routes for webhooks. It defines routes at telegram/#{hash_of(bot.token)} and connects bots with controller.

# Most off apps would require
telegram_webhook TelegramController
# which is same as
telegram_webhook TelegramController, :default

# Use different controllers for each bot:
telegram_webhook TelegramChatController, :chat
telegram_webhook TelegramAuctionController, :auction

# Defined route is named and its name depends on `Telegram.bots`.
# For single bot it will use 'telegram_webhook',
# for multiple bots it uses bot's key in the `Telegram.bots` as prefix
# (eg. `chat_telegram_webhook`).
# You can override this with `as` option:
telegram_webhook TelegramController, as: :custom_telegram_webhook

Processing updates

To process update with controller call .dispatch(bot, update) on it. There are several options to run it automatically:

  • Use webhooks with routes helper (described above).
  • Use Telegram::Bot::Middleware with rack (example in wiki).
  • Use poller (described in the next section).

When controller runs in webhook mode Telegram::Bot::Middleware passes ActionDispatch::Request object to the .dispatch method. It's available via #webhook_request controller method.

To run action without update (ex., send notifications from jobs), you can call #process directly. In this case controller can be initialized with :from and/or :chat options instead of update object:

controller = ControllerClass.new(bot, from: telegram_user, chat: telegram_chat)
controller.process(:welcome, *args)

Development & Debugging

Use rake telegram:bot:poller to run poller in rails app. It automatically loads changes without restart in development env. Optionally pass bot id in BOT envvar (BOT=chat) to specify bot to run poller for.

This task requires telegram_webhook helper to be used as it connects bots with controller. To run poller in other cases use:

Telegram::Bot::UpdatesPoller.start(bot, controller_class)

Testing

There is a Telegram::Bot::ClientStub class to stub client for tests. Instead of performing API requests it stores them in a requests hash.

To stub all possible clients use Telegram::Bot::ClientStub.stub_all! before initializing clients. Here is a template for RSpec:

# environments/test.rb
# Make sure to run it before defining routes or accessing any bot in the app!
Telegram.reset_bots
Telegram::Bot::ClientStub.stub_all!

# rails_helper.rb
RSpec.configure do |config|
  # ...
  config.after { Telegram.bot.reset }
  # or for multiple bots:
  config.after { Telegram.bots.each_value(&:reset) }
  # ...
end

RSpec contexts and helpers are included automatically for groups and examples with matching tags. In RSpec < 3.4 it's required to use include_context explicitly. See list of available helpers for details.

There are 3 types of integration tests:

  • :rails - for testing bot in webhooks-mode in Rails application. It simulates webhook requests POSTing data to controller's endpoint. It works on the top of requests specs, so rspec-rails gem is required.
  • :rack - For testing bot in webhooks-mode in non-Rails application. It uses rack-test gem to POST requests to bot's endpoint.
  • :poller - Calls .dispatch directly on controller class.

Pick the appropriate one, then require telegram/bot/rspec/integration/#{type} and mark spec group with tag telegram_bot: type. See configuration options for each type in telegram/bot/rspec/integration/.

Here is an example test for a Rails app:

# spec/requests/telegram_webhooks_spec.rb
require 'telegram/bot/rspec/integration/rails'

RSpec.describe TelegramWebhooksController, telegram_bot: :rails do
  # for old RSpec:
  # include_context 'telegram/bot/integration/rails'

  # Main method is #dispatch(update). Some helpers are:
  #   dispatch_message(text, options = {})
  #   dispatch_command(cmd, *args)

  # Available matchers can be found in Telegram::Bot::RSpec::ClientMatchers.
  it 'shows usage of basic matchers' do
    # The most basic one is #make_telegram_request(bot, action).
    # It works similar to `receive` matcher and supports chaining `.with(args).exactly(n).times`.
    expect { dispatch_command(:start) }.to make_telegram_request(bot, :sendMessage).
      with(hash_including(text: 'msg text'))

    # There are some shortcuts for dispatching basic updates and testing responses.
    expect { dispatch_message('Hi') }.to send_telegram_message(bot, /msg regexp/, some: :option)
  end

  describe '#start!' do
    subject { -> { dispatch_command :start } }
    # Using built in matcher for `respond_to`:
    it { should respond_with_message 'Hi there!' }
  end

  # There is context for callback queries with related matchers,
  # use :callback_query tag to include it.
  describe '#hey_callback_query', :callback_query do
    let(:data) { "hey:#{name}" }
    let(:name) { 'Joe' }
    it { should answer_callback_query('Hey Joe') }
    it { should edit_current_message :text, text: 'Done' }
  end
end

There is a context for testing bot controller in the way similar to Rails controller tests. It's supposed to be a low-level alternative for integration tests. Among the differences is that controller tests use a single controller instance for all dispatches in specific example, session is stubbed (does not use configured store engine), and update is not serialized so it also supports mocks. This can be useful for unit testing, but should not be used as the default way to test the bot.

require 'telegram/bot/updates_controller/rspec_helpers'
RSpec.describe TelegramWebhooksController, type: :telegram_bot_controller do
  # for old RSpec:
  # include_context 'telegram/bot/updates_controller'

  # Same helpers and matchers like dispatch_command, answer_callback_query are available here.
end

See sample app for more examples.

Deployment

While webhooks-mode is prefered, poller still can be used in production. See comparison and examples for details.

Async mode

There is built in support for async requests using ActiveJob. Without Rails you can implement your own worker class to handle such requests. This allows:

  • Process updates very fast, without waiting for telegram responses.
  • Handle and retry network and other errors with queue adapter.
  • ???

Instead of performing request instantly client serializes it, pushes to queue, and immediately return control back. The job is then fetched with a worker and real API request is performed. And this all is absolutely transparent for the app.

To enable this mode add async: true to bot's config. For more information and custom configuration check out docs or source.

If you want async mode, but don't want to setup queue, know that Rails 5 are shipped with Async adapter by default, and there is Sucker Punch for Rails 4.

To disable async mode for the block of code use bot.async(false) { bot.send_photo }. Yes, it's threadsafe too.

Limitations

  • Client will not return API response.
  • Sending files is not available in async mode, because they can not be serialized.

Development

After checking out the repo, run bin/setup to install dependencies and git hooks. Then, run appraisal 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.

Use bin/fetch-telegram-methods to update API methods list from Telegram website.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/telegram-bot-rb/telegram-bot.

telegram-bot's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

telegram-bot's Issues

Reply to Telegram requests directly in webhook mode

Due to official FAQ https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates, it's possible to reply direcly to Telegram requests in webhook mode. It's a preferrable way for highly-loadded bots because of reducing API calls.

As I can see, there is no way to work in such mode with your library.

I suggest to implement render method in UpdatesController and allow users to respond to Telegram requests direcly.
It will solve few problems:

  • Reduce overall count of requests to Telegram API(do you remember about 429?)
  • Allow users to move bot logic from controller to their own handlers/classes/etc without pain and test it the their own manner

In poller mode render method can work as respond_with and use API call

Usage with Rails 5.1

How to use the gem with rails 5.1.0? I've tried to install gem 'telegram-bot', but got error

telegram-bot was resolved to 0.11.2, which depends on
      actionpack (< 5.1, >= 4.0)

Error in rake telegram:bot:poller when using response typecasting

Hi!
I'm using response typecasting. Following is my /config/initializers/telegram_bot.rb I use to set things up:

Telegram.bots_config = MyApp.config.fetch('telegram_bot', {}).symbolize_keys
Telegram::Bot::Client.typed_response!

And I got following error when running rake telegram:bot:poller:

$ bin/rake telegram:bot:poller
Running via Spring preloader in process 26085
Started bot poller.
no implicit conversion of String into Integer
/home/mike/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/telegram-bot-0.11.0/lib/telegram/bot/updates_poller.rb:68:in `[]'
/home/mike/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/telegram-bot-0.11.0/lib/telegram/bot/updates_poller.rb:68:in `fetch_updates'
/home/mike/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/telegram-bot-0.11.0/lib/telegram/bot/updates_poller.rb:48:in `start'
...

because it was waiting for Hash, not Array of Telegram::Bot::Types::Update.

Very intricate startup worflow

As I can see, there are some tricks around loading the library and bot's controller. I found this when tried to use simplecov gem for my tests. Due to #56 I had to include my helper modules explicitly and noticed, that I don't see the helper in simplecov report.

Seems like the controller code is executed before SimpleCov.start.

ArgumentError: Missing host to link to! after rake telegram:bot:set_webhook

Hello,

I try to set webhook on production server, but get 'missing host' error, even though host is set both production.rb. Same problem appears on dev machine with :host=>'localhost:3000'.

denker@ubuntu-timzuev:~/deploy/lifetracker/current$ rake telegram:bot:set_webhook RAILS_ENV=production
rake aborted!
ArgumentError: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
/home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/http/url.rb:62:in 'full_url_for'
/home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/http/url.rb:52:in url_for' /home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/route_set.rb:303:inblock in class:RouteSet'
/home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/route_set.rb:175:in call' /home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/route_set.rb:295:inblock (2 levels) in define_url_helper'
/home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/telegram-bot-0.8.0/lib/tasks/telegram-bot.rake:22:in block (4 levels) in <top (required)>' /home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/telegram-bot-0.8.0/lib/tasks/telegram-bot.rake:20:ineach'
/home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/telegram-bot-0.8.0/lib/tasks/telegram-bot.rake:20:in block (3 levels) in <top (required)>' /home/denker/deploy/lifetracker/shared/bundle/ruby/2.3.0/gems/rake-11.2.2/exe/rake:27:in<top (required)>'
/home/denker/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in eval' /home/denker/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in

'
Tasks: TOP => telegram:bot:set_webhook
(See full trace by running task with --trace)

Updates Poller error in Rails 5

I am getting the following error when trying to run the poller (rails 5)

wrong number of arguments (given 2, expected 3)
/Users/arpit/.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.1/lib/action_controller/metal.rb:258:in `dispatch'
/Users/arpit/.rvm/gems/ruby-2.3.0/gems/telegram-bot-0.8.0/lib/telegram/bot/updates_poller.rb:49:in `block in start'
/Users/arpit/.rvm/gems/ruby-2.3.0/gems/telegram-bot-0.8.0/lib/telegram/bot/updates_poller.rb:72:in `block (2 levels) in fetch_updates'
/Users/arpit/.rvm/gems/ruby-2.3.0/gems/telegram-bot-0.8.0/lib/telegram/bot/updates_poller.rb:70:in `each'
...

This looks like because of an API change in the metal.rb class in Rails 5 where the signature changed from 2 to 3 arguments

    def dispatch(name, request, response) #:nodoc:
      set_request!(request)
      set_response!(response)
      process(name)
      request.commit_flash
      to_a
    end

https://github.com/rails/rails/blob/5-0-stable/actionpack/lib/action_controller/metal.rb

Not sure if this is a known issue and there is a workaround ?

Error when running poller in production

RAILS_ENV=production bundle exec rake telegram:bot:poller
rake aborted!
Poller not found for :default
/usr/local/opt/rbenv/versions/2.3.0/gemsets/mastermind/gems/telegram-bot-0.6.0/lib/telegram/bot/updates_poller.rb:21:in `start'
/usr/local/opt/rbenv/versions/2.3.0/gemsets/mastermind/gems/telegram-bot-0.6.0/lib/tasks/telegram-bot.rake:7:in `block (3 levels) in <top (required)>'
/usr/local/opt/rbenv/versions/2.3.0/bin/bundle:23:in `load'
/usr/local/opt/rbenv/versions/2.3.0/bin/bundle:23:in `<main>'
Tasks: TOP => telegram:bot:poller
(See full trace by running task with --trace)

When running in the development environment everything works fine.

Secrets are as follows:

development:
  secret_key_base: d873a75e86e73cb5f0f18961b08d2ea721fe676b050e35caeee3c1e8afb96f62a76f7c8f041417f4f26dc1895831c8dc492589a5f599d14155ae8cf8a33b2e2b
  telegram:
    bot:
      token: <%= ENV["TELEGRAM_BOT_TOKEN"] %>
      username: bot_name

test:
  telegram:
    bot:
      token: <%= ENV["TELEGRAM_BOT_TOKEN"] %>
      username: bot_name
  secret_key_base: cd7f42ca1d03372280e76cd0e073970bd3522f5986f6a2eccba26782ac66ffcd30655abea290aba9291ec8b75b690b1d3ccfe1451942367453e9d4e867e58466

production:
  telegram:
    bot:
      token: <%= ENV["TELEGRAM_BOT_TOKEN"] %>
      username: bot_name
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

SSL errors

When I run my bot with RAILS_ENV=production rails server -p 443 after having used rake telegram:bot:set_webhook RAILS_ENV=production CERT=~/Certificate/YOURPUBLIC.pem, the Telegram api reports at getWebhookInfo the following error:

"last_error_message":"SSL error {336130315, error:1408F10B:SSL routines:ssl3_get_record:wrong version number}"

What am I doing wrong?

The server prints the following error:

2018-04-10 23:23:02 +0200: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>

Any idea?

I use routes.default_url_options = {host: 'MY_IP:443', protocol: 'https'} in config/environments/production.rb

context_handler input 'Text' with new line problem

I want input long text with new line in chat room.
So I write code like this.

context_handler :mobile_message do |*words|
    context = :mobile_message
    session[context] = words
    puts "@@@@start@@@@@"
    puts "session: #{session[:mobile_message]}"
    puts "@@@@@end@@@@@@"
    respond_with :message, text: "are you sure?", reply_markup: {
      inline_keyboard: [
        [{text: "confirm!", callback_data: 'send_mobile_message:confirm'}],
        [{text: "no", callback_data: 'send_mobile_message:cancel'}]
      ],
      resize_keyboard: true,
    }
  end

but session[:mobile_message] is array with every words that seperated with space without new line how can I do?

Not possible to use rescue_from in controller

`<class:TelegramWebhooksController>': undefined method `rescue_from' for TelegramWebhooksController:Class (NoMethodError)

I know that it's possible to include ActiveSupport::Rescuable manually, but it does't work.

Bot not configured

Hi @printercu, I am using Heroku to deploy my new app, when I were attempting to deploy it I got an error like this:

remote: -----> Preparing app for Rails asset pipeline
remote:        Running: rake assets:precompile
remote:        rake aborted!
remote:        Telegram::Bot::Client :benny not configured
remote:        /tmp/build_0e15efd91b39858d4bff32e7fd9896a6/vendor/bundle/ruby/2.4.0/gems/telegram-bot-0.12.1/lib/telegram/bot/initializers.rb:7:in `wrap'
remote:        /tmp/build_0e15efd91b39858d4bff32e7fd9896a6/vendor/bundle/ruby/2.4.0/gems/telegram-bot-0.12.1/lib/telegram/bot/routes_helper.rb:54:in `block in telegram_webhooks'
remote:        /tmp/build_0e15efd91b39858d4bff32e7fd9896a6/vendor/bundle/ruby/2.4.0/gems/telegram-bot-0.12.1/lib/telegram/bot/routes_helper.rb:53:in `each'
remote:        /tmp/build_0e15efd91b39858d4bff32e7fd9896a6/vendor/bundle/ruby/2.4.0/gems/telegram-bot-0.12.1/lib/telegram/bot/routes_helper.rb:53:in `telegram_webhooks'
remote:        /tmp/build_0e15efd91b39858d4bff32e7fd9896a6/config/routes.rb:4:in `block in <top (required)>'

Here is my routes.rb:

# frozen_string_literal: true

Rails.application.routes.draw do
  telegram_webhooks benny: TelegramBot::BennyController
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

It runs well when using rake telegram:bot:poller at my development (local) environment. Did I do anything wrong?

Assets not working/incompatible controllers with webpage?

Hi!

I have decided to take my bot a bit further and increase the rails app to be used not only as a telegram bot but also be able to be its own webapp. I have managed to reorganize the telegram part of it so the routs go to the proper controllers and keep them organized inside a different scope but I am having an issue which is driving me crazy.

I have taken a sekeleton I use with bootstrap and a them that I have and the first step was to plug it in and start tweaking it a bit but it seems that there is no way I can get the assets pipeline working properly. For isntance, every time I use the img_tag helper I get a path like if the assets hasn't been compiled.

For CSS and Js I hav no issue but for figures there is no way I can get the figures loaded properly. I have a logo under /assets/images/logos/logo_small.png for example and when I do:

<div class="brand-logo">
<%= image_tag "logos/join_text_wite_50.png", alt: "Join logo", class: 'img-responsive' %>
</div>

I get the following HTML:

<div class="brand-logo-collapsed">
<img alt="Join logo" class="img-responsive" src="/images/logos/join_text_wite_50.png">
</div>

which gives a 404 error because that path does not exists. After a lot of trying to figure out where the issue is I thought that maybe it is best to ask in case there is some configuration in the example rails app environments and setup which I sed to start my bot which is not allowing for a good assets pipelines behaviour.

Do you know if there is any issue with this?

Thanks!

how do i download a file?

i can use something like this:

def message(message)
  bot.get_file message['document']
end

but all i get from that is another hash like this:

{"ok"=>true, "result"=>{"file_id"=>"xxxxx", "file_size"=>12121, "file_path"=>"documents/file_5.ext"}}

how can i get the actual contents of the file? they don't seem to be stored anywhere and i can't find any reference on the gem's code

login with telegram

Like the other system
I am interest to find a way user signup with telegram.
I think if there is a form user put phone number on it and get a for number as token in telegram.
when enter token login and this scenario continue each time.
but seems that if user dont start a bot we can send massage to him. do you know any thing about it ?

Timeout issue in Heroku for long request

Hi!

I just have an issue within heroku and I would like to know if you guys know a way of overcome this.

I have an option that only me as an admin can run to send an update to everyone in the bot. The issue is that when I run it it takes more than 30 seconds to run which is the timeout in Heroku. Nonethless Heroku supports long polling and straming so this things can be avoidd but what is needed is the following:

Heroku supports HTTP 1.1 features such as long-polling and streaming responses. An application has an initial 30 second window to respond with a single byte back to the client. However, each byte transmitted thereafter (either received from the client or sent by your application) resets a rolling 55 second window. If no data is sent during the 55 second window, the connection will be terminated.

As explained here: https://devcenter.heroku.com/articles/request-timeout

I just would like to know if you what is consider whn talking about a rails app made with this tool a "response back to the client".

build_session fails on Rails 5.2 API flag

I get RuntimeError (session_store is not configured) when the webhook receive a request. I create a project using API flag, using gem 'jwt' and route versioning. Could someone help me with this issue?

rails

Message context doesn't run

Hi again there, I want to ask whether is there something wrong with the message context or am I doing it wrong. I am implementing message context from the readme and tested it manually but it didn't run well. Here is the screenshot how I did it:

screen shot 2017-05-28 at 17 19 10

Here is the controller I am using:

module TelegramBot
  class FlipFlopController < Telegram::Bot::UpdatesController
    include TelegramBot::Constant
    include TelegramBot::FlipFlop
    include Telegram::Bot::UpdatesController::MessageContext
    context_to_action!

    # i also tried this
    # def start(*args)
    #   if args.any?
    #     respond_with :message, text: t('.start.done')
    #   else
    #     save_context :start
    #     respond_with :message, text: t('.start.context')
    #   end
    # end

    def start(user1 = nil, user2 = nil, *args)
      if start_valid?(user1, user2, args)
        if user1 && user2
          start_ff(user1, user2)
        else
          save_context :start
          respond_with :message, text: t('.start.context')
        end
      else
        help_message
      end
    end
end

Stop controller execution

Hi. I build my bot that contains before_filter function that checks if user banned or not. So, I'm stuck with the problem: without extending controller from AbstractController my application won't stop execution in before_filter method. I tried to include ActionView::Helpers::RenderingHelper (f.e. to use render :nothing => true, :status => 200 ) and other modules, but none of them works.. How can I do it?

RSpec Integration for multiple bots project

Hi there, I want to ask is there any example of how to use the RSpec testing within a project with multiple bots inside it.

For now, I have a project using multiple bots definition in secrets.yml but yet I defined just one bot inside it. Like this:

test:
  secret_key_base: <string>
  telegram:
    bots:
      mybot:
        token: <bot_token>
        username: <bot_uname>

I followed the RSpec example inside your example app. Here is my spec file:

RSpec.describe TelegramBot::MyBotController, :telegram_bot do
  let(:prefix) { 'telegram_bot.mybot' }

  describe '#start' do
    subject { -> { dispatch_command :start } }
    it { should respond_with_message I18n.t("#{prefix}.start") }
  end
end

But yet, when I run the rspec it got the following error:

TelegramBot::MyBotController#start should send telegram message "Template string"
Failure/Error: it { should respond_with_message I18n.t("#{prefix}.start") }

NoMethodError:
  undefined method `requests' for nil:NilClass
# /Users/arsoedjono/.rvm/gems/ruby-2.3.0/gems/telegram-bot-0.11.3/lib/telegram/bot/rspec/client_matchers.rb:73:in `matches?'
# ./spec/telegram_bots/mybot_spec.rb:6:in `block (3 levels) in <top (required)>'

Is it not yet supported for multiple bots project? Or is there any alternatives I could use? I am looking forward to the answer, coz this gem is awesome and I like it.

Best regards

Save context outside from the controller

Hi again, I'm trying to call action without update from my ActiveJob, something like:

def perform(*args)
  bot = Telegram::Bot::Client.new("#{BOT_API}", "#{BOT_USERNAME")
  controller = SomeController.new(bot, from: "#{user.tg_id}", chat: "#{user.tg_id}")
  controller.process(:test, "blabla")
end

And my action looks like:

def test(message=nil, *)
  save_context :test
  respond_with :message, text: message
end

But save_context won't work. I tried to debug session in that place, so I get
#<Telegram::Bot::UpdatesController::Session::SessionHash:0x2495690 not yet loaded>
What can I do in this situation?

Don't know how to use different controllers while still getting session

Hi,

I want to create different controllers since my bot functinalities are getting too big and becoming hard to manage only in one controller. My approach is to use what is mentioned here, the first case, but I got the following exception:

session_store is not configured

How should I proceed to properly find how to deal with it?
Thanks!

Example how to use it.

It would be nice to have an example project showing the confs for the bot to work.
I've been trying to setup a new rails project with this library and I could not get it working properly, as I'm new to rails development.

Any help would be appreciated. Thanks.

Session doesn't work on Heroku

Hi,

I've built a simple Bot using your great gem. It works fine locally but on production instead (I'm on Heroku) it doesn't seem to store the session.

In particular, when I run the command /gym_update_count and I reply, it replies with the default message.

This is my method:

def gym_update_count(*)
    save_context(:gym_count)
    respond_with :message, text: "How many times have you been to the gym in total?"
end

context_handler :gym_count do |count = nil, *|
    user.update(times_in_gym: count)
    respond_with :message, text: '👍 Updated'
end

I believe the problem lies in the cache.
I've added RedisCloud to my account, added gem 'redis-rails', '~> 5' and changed my production.rb file with:

config.cache_store = :redis_store, ENV['REDISCLOUD_URL']
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}

What am I doing wrong?

Thanks

Sidekiq

Hello
I'm using sidekiq with my bot and want to move some logic, data fetching and responses to workers. How can I pass bot and payload from controller to workers?

Send the message at specific time or when time lapses.

I am sorry, if the question is basic, but as far as I saw the methods of this gem, sends replies, when it receives an input from the user.

Does this gem support the feature like, sending message at a specific time or every hour or when the time lapses?

Missing symbols in payload.text?

Hi, got some stranges:
Sent message to bot:

👨‍💻first line 👨‍💻
✈️second line 🛫
✅third line ✅
💵try to reproduce💵
@username https://url.url

Got next:

[4] pry> payload.text
=> "👨💻first line 👨💻\n" + "✈️second line 🛫\n" + "✅third line ✅\n" + "💵try to reproduce💵\n" + "@username https://url.url"
[3] pry> payload.entities
=> [#<Telegram::Bot::Types::MessageEntity:0x0000565515c08d78 @length=9, @offset=74, @type="mention", @url=nil, @user=nil>,
 #<Telegram::Bot::Types::MessageEntity:0x0000565515c08af8 @length=15, @offset=84, @type="url", @url=nil, @user=nil>]
[5] pry> payload.text.size
=> 92
[11] pry> payload.text[84,15]
=> "/url.url"
[12] pry> payload.text[74,9]
=> "me https:"

Same bug with typecasting turned off.
Ruby 2.5.

send photo and html text

Hi! when i use reply_with :photo, photo: File.open('party.jpg') there is an error on console:
No such file or directory @ rb_sysopen
Where should I put the images? I'm using the route: app/assets/images/

And other thing: its possible use html text in responses? how i do it from controller? I only know do it from views

sequence question

I know this is not issues but I have a question and I will tankful if you can help me. 👍
I can't understand how to work if we wan't to get information from user.
for example if we want to ask job application form from user , like robot ask please give me your full name , after save it to database ask what is your degree and so on
I wrote this kind of code:

respond_with :message, text: 'PLEASE CHOSE A PASSWORD'
User.where(username: "#{from['id']}", encrypted_password: create_pwd("#{from['text']}") ).first_or_create

but before user answer question a record save to database, I mean respond_with , don't wait for answer and continue.

tanks

RSpec for multiple bots only applied for first defined bot

Hi there, I finally enter my second bot to my project. And when I run rspec it always failed for my second bot scenarios. I am trying to debug it and I found it both entering the same controller. And yet I found the controller_path having the value of /telegram instead of /telegram/{token}. Maybe it is the root of cause?

Best regards.

Failing Example Spec

I have this spec from examples:

RSpec.describe TelegramWebhooksController, :telegram_bot do
  describe '#start' do
    subject { -> { dispatch_message '/start' } }
    it { is_expected.to respond_with_message 'Hi!' }
  end
end

But it fails:

Failure/Error: it { is_expected.to respond_with_message 'Hi!' }
  expected to make exactly 1 #<Telegram::Bot::ClientStub#70315011902840()>.sendMessage requests, with [#<RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher:0x00007fe6fa9d1488 @expected={:chat_id=>456, :text=>"Hi!"}>], but made 0, and 0 with

How can I fix it?

Unable to retrieve settings from secrets.yml

Hi there!
I have a new empty rails application with telegram-bot installed. And the problem is telegram_config is empty despite the options in secrets.yml

There's my secrets.yml:

development:
  secret_key_base: <secret key base>
  telegram:
    bots:
      main: <here goes my bot api key>

And in rails console I get this:

#secrets are here, everything is fine
[3] pry(main)> Rails.application.secrets[:telegram]
=> {:bots=>{:main=>"378827954:AAFl-3Zk0xHF3McEmf2sOYnteZShxxZAUGY"}}
[4] pry(main)> 

#but telegram config is empty
[1] pry(main)> Telegram.bots
=> {}

I've tried to figure out what's happening and the reason is string hash keys there:

(telegram_config['bots'] || {}).symbolize_keys.tap do |config|

Thank you!

Editing messages

Is there any better way to edit messages inside the controller than that?

Telegram.bot.edit_message_text(chat_id: from['id'], text: "edited message", message_id: payload['message']['message_id'])

Helpers are not included by default

I have a few helper modules in app/helpers and they are not available from controller by default, even ApplicationHelper. I have to include them explicitly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.