1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-09-03 17:55:52 +03:00

Сравнить коммиты

..

46 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
07faf87ed7 Merge pull request #163 from telegram-bot-rb/update_api
Update to Bot API 5.3
2021-06-25 19:43:17 +01:00
Max Melentiev
ac45940937 Switch from travis-ci.org to travis-ci.com 2021-06-25 19:42:59 +01:00
Max Melentiev
a49ef51da9 Update to Bot API 5.3 2021-06-25 19:31:28 +01:00
Max Melentiev
4f669fccc4 Merge pull request #157 from AlexVPopov/table-of-contents
Add a table of contents to README
2021-03-31 20:56:59 +01:00
Alexander Popov
e06252907a Add a table of contents to README 2021-03-31 21:11:08 +03:00
Max Melentiev
af229c8bcc Fix typos in readme, make some points more clear 2021-02-19 09:38:54 +00:00
Max Melentiev
a7cf3754be v0.15.3 2021-02-15 13:54:10 +00:00
Max Melentiev
783d9c13e9 Merge pull request #156 from telegram-bot-rb/ruby-3
Fix translation helpers to support ruby 3
2021-02-15 13:52:09 +00:00
Max Melentiev
5417511f06 Fix translation helpers to support ruby 3 2021-02-15 09:44:19 +00:00
Max Melentiev
a0ca383bab v0.15.2 2021-01-26 08:37:02 +00:00
Max Melentiev
1c141f782e Merge pull request #153 from telegram-bot-rb/ruby-3.0
Ruby 3.0 support
2021-01-26 08:30:24 +00:00
Max Melentiev
791c4233ca Ruby 3.0 support, drop support for Ruby < 2.4 2021-01-09 11:35:13 +00:00
Max Melentiev
036fbd20d5 v0.15.1 2020-12-20 08:25:35 +03:00
Max Melentiev
0590967694 Merge pull request #150 from telegram-bot-rb/rails-61
Add rails 6.1 support
2020-12-20 05:23:57 +00:00
Max Melentiev
4ab1f07bed Add rails 6.1 support 2020-12-11 21:00:15 +03:00
Max Melentiev
8cd948a544 v0.15.0 2020-12-06 18:37:47 +03:00
Max Melentiev
863e838533 Remove deprecated rspec/integration 2020-12-06 18:35:10 +03:00
Max Melentiev
2a2fafa536 Merge pull request #147 from telegram-bot-rb/update_api
Update to Bot API 5.0
2020-12-06 15:32:58 +00:00
Max Melentiev
4b9eedb5a2 Add server option for client to support local bot API servers 2020-11-27 18:47:47 +00:00
Max Melentiev
f654071c4c Drop Initializer module
It was required for Botan support.
2020-11-27 09:39:40 +00:00
Max Melentiev
e178f808b6 Update to Bot API 5.0, add rake tasks for deleteWebhook, close & logOut 2020-11-27 09:33:39 +00:00
Max Melentiev
d173443c56 Merge pull request #142 from telegram-bot-rb/path_with_hashed_token
Use token hash in routes instead of plain value, support :path option
2020-09-26 16:14:04 +01:00
Max Melentiev
97e45d94f2 Use token hash in routes instead of plain value, support :path option 2020-09-06 20:13:08 +01:00
Max Melentiev
03472e3d46 Merge pull request #141 from astrophena/updates-poller-typo
Fix a typo in updates poller
2020-07-25 15:29:59 +03:00
Ilya Mateyko
bc2b09a3b0 Fix a typo in updates poller 2020-07-23 01:09:13 +03:00
Max Melentiev
8494c7df0c v0.14.4 2020-04-15 20:40:55 +03:00
Max Melentiev
a2659e00c2 Bump rake version to resolve vulnerability notification 2020-04-04 22:36:41 +03:00
Max Melentiev
e43c611d9d Merge pull request #131 from telegram-bot-rb/update_api
Update to Bot API 4.7
2020-04-04 22:35:33 +03:00
Max Melentiev
b84c48bc1d Update to Bot API 4.7 2020-04-04 22:26:26 +03:00
Max Melentiev
43cd096a21 Fix spelling in Async docs 2020-01-25 15:49:28 +03:00
Maximilian Haack
903e14a3e5 Fix make_telegram_request examples in README (#129) 2020-01-25 13:34:48 +03:00
Max Melentiev
da31cd148a v0.14.3 2019-08-24 19:34:34 +03:00
Max Melentiev
552f3b3353 Update to Bot API 4.4 2019-08-24 19:33:46 +03:00
Max Melentiev
e5f93f608b Merge pull request #121 from telegram-bot-rb/rails_60
Require tests against rails 6.0, run tests against rails 6.1.alpha
2019-08-19 22:10:50 +03:00
Max Melentiev
7e823b4354 Require tests against rails 6.0, run tests against rails 6.1.alpha
Rails 6.0 requires ruby >= 2.5, so ruby 2.4 is not tested by default
and replaced with 2.6
2019-08-19 11:06:48 +03:00
Max Melentiev
ca4ea9e6cb v0.14.2 2019-06-30 21:15:53 +03:00
Max Melentiev
68ec0257bd Add note on editing rails 6.0 credential 2019-06-30 21:10:22 +03:00
Max Melentiev
b50bd30137 Add methods from api v4.3 2019-06-30 20:57:50 +03:00
Max Melentiev
80362da270 Add answer_shipping_query helper 2019-06-30 20:56:41 +03:00
Muhammad Nuzaihan Bin Kamal Luddin
294d762785 Add answer_pre_checkout_query helper (#118) 2019-06-30 20:39:43 +03:00
printercu
1cb3265371 Merge pull request #113 from telegram-bot-rb/fix_travis
Remove workaround for ruby 2.5.0 to fix travis builds
2019-02-15 20:03:31 +05:30
Max Melentiev
c44529725f Relax bundler dependency 2019-02-15 19:46:01 +05:30
Max Melentiev
fa43561765 Remove workaround for ruby 2.5.0 to fix travis builds 2019-02-15 19:27:18 +05:30
printercu
cea856c928 Merge pull request #110 from biow0lf/master
Fix typo
2019-02-15 19:25:32 +05:30
Igor Zubkov
5a93c1efc6 Fix typo 2019-01-24 03:47:05 +02:00
Max Melentiev
38ee303912 Fix doc-comment for latest changes 2018-09-23 10:39:56 +03:00
40 изменённых файлов: 693 добавлений и 341 удалений

Просмотреть файл

@@ -40,6 +40,7 @@ Style/IfUnlessModifier: {Enabled: false}
# Consistent to other definitions. # Consistent to other definitions.
Style/EmptyMethod: {EnforcedStyle: expanded} Style/EmptyMethod: {EnforcedStyle: expanded}
Style/Lambda: {EnforcedStyle: literal}
Style/ModuleFunction: {Enabled: false} Style/ModuleFunction: {Enabled: false}
Style/NestedParenthesizedCalls: {Enabled: false} Style/NestedParenthesizedCalls: {Enabled: false}
Style/SignalException: {EnforcedStyle: only_raise} Style/SignalException: {EnforcedStyle: only_raise}

Просмотреть файл

@@ -1,34 +1,30 @@
os: linux
dist: xenial
language: ruby language: ruby
cache: bundler cache: bundler
rvm: rvm:
- 2.6
- 2.5 - 2.5
- 2.4
gemfile: gemfile:
- gemfiles/rails_61.gemfile
- gemfiles/rails_60.gemfile
- gemfiles/rails_52.gemfile - gemfiles/rails_52.gemfile
- gemfiles/rails_51.gemfile jobs:
matrix:
include: include:
# ruby 3.x
- rvm: 3.0
gemfile: gemfiles/rails_61.gemfile
- rvm: 3.0
gemfile: gemfiles/rails_60.gemfile
# old rails # old rails
- rvm: 2.5
gemfile: gemfiles/rails_51.gemfile
- rvm: 2.5 - rvm: 2.5
gemfile: gemfiles/rails_50.gemfile gemfile: gemfiles/rails_50.gemfile
- rvm: 2.5 - rvm: 2.5
gemfile: gemfiles/rails_42.gemfile gemfile: gemfiles/rails_42.gemfile
# old rubies # old rubies
- rvm: 2.3 - rvm: 2.4
gemfile: gemfiles/rails_52.gemfile gemfile: gemfiles/rails_52.gemfile
# future rails
- rvm: 2.5
gemfile: gemfiles/rails_60.gemfile
# Allow rails 6 to fail while master branch is used
allow_failures:
- rvm: 2.5
gemfile: gemfiles/rails_60.gemfile
notifications: notifications:
email: false email: false
# for 2.5.0 until 2.5.1 is released:
# https://github.com/travis-ci/travis-ci/issues/8978
# https://github.com/travis-ci/travis-ci/issues/8969#issuecomment-354135622
before_install:
- gem update --system
- gem install bundler

Просмотреть файл

@@ -1,26 +1,29 @@
appraise 'rails-61' do
gem 'actionpack', '~> 6.1.0'
gem 'railties', '~> 6.1.0'
end
appraise 'rails-60' do appraise 'rails-60' do
git 'https://github.com/rails/rails.git' do gem 'actionpack', '~> 6.0.0'
gem 'actionpack', '~> 6.0.0.alpha' gem 'railties', '~> 6.0.0'
gem 'railties', '~> 6.0.0.alpha'
end
end end
appraise 'rails-52' do appraise 'rails-52' do
gem 'actionpack', '~> 5.2.0.beta2' gem 'actionpack', '~> 5.2.0'
gem 'railties', '~> 5.2.0.beta2' gem 'railties', '~> 5.2.0'
end end
appraise 'rails-51' do appraise 'rails-51' do
gem 'actionpack', '~> 5.1' gem 'actionpack', '~> 5.1.0'
gem 'railties', '~> 5.1' gem 'railties', '~> 5.1.0'
end end
appraise 'rails-50' do appraise 'rails-50' do
gem 'actionpack', '~> 5.0' gem 'actionpack', '~> 5.0.0'
gem 'railties', '~> 5.0' gem 'railties', '~> 5.0.0'
end end
appraise 'rails-42' do appraise 'rails-42' do
gem 'actionpack', '~> 4.2' gem 'actionpack', '~> 4.2.0'
gem 'railties', '~> 4.2' gem 'railties', '~> 4.2.0'
end end

Просмотреть файл

@@ -1,8 +1,49 @@
# Unreleased # Unreleased
# 0.15.4
- Update to Bot API 5.3
# 0.15.3
- Ruby 3.0 support: fix translation helpers.
# 0.15.2
- Ruby 3.0 support. Drop support for Ruby < 2.4.
# 0.15.1
- Rails 6.1 support.
# 0.15.0
- Add `:path` option to `telegram_webhook` route helper.
- __Breaking change!__ Default route is generated using hashed bot token.
Please reconfigure webhook after update (`rake telegram:bot:set_webhook`).
- Update to Bot API 5.0, add rake tasks for `deleteWebhook`, `close` & `logOut`.
- Add `server` option for client to support local bot API servers.
# 0.14.4
- Update to Bot API 4.7
# 0.14.3
- Rails 6.0 support.
- Update to Bot API 4.4.
# 0.14.2
- Add reply helpers: `answer_pre_checkout_query`, `answer_shipping_query`.
- Update to Bot API 4.3.
# 0.14.1
- Read config from secrets when credentials don't provide it in rails >= 5.2. - Read config from secrets when credentials don't provide it in rails >= 5.2.
- Remove botan.io support. It's already shut down, so it should not be a braking change. - Remove botan.io support. It's already shut down, so it should not be a braking change.
https://github.com/botanio/sdk#this-service-will-be-shut-down-on-25th-may-2018 https://github.com/botanio/sdk#this-service-will-be-shut-down-on-25th-may-2018
- Update to Bot API 4.1.
# 0.14.0 # 0.14.0

16
Gemfile
Просмотреть файл

@@ -4,17 +4,17 @@ gemspec
group :development do group :development do
gem 'appraisal', '~> 2.2' gem 'appraisal', '~> 2.2'
gem 'pry', '~> 0.10.1' gem 'pry', '~> 0.13.1'
gem 'pry-byebug', '~> 3.2.0' gem 'pry-byebug', '~> 3.9.0'
gem 'sdoc', '~> 0.4.1' gem 'sdoc', '~> 2.0.3'
gem 'telegram-bot-types', '~> 0.6.0' gem 'telegram-bot-types', '~> 0.6.3'
gem 'rspec', '~> 3.5.0' gem 'rspec', '~> 3.10.0'
gem 'rspec-its', '~> 1.1.0' gem 'rspec-its', '~> 1.3.0'
gem 'rspec-rails', '~> 3.5.0' gem 'rspec-rails', '~> 4.0.2'
gem 'rubocop', '~> 0.52.1' gem 'rubocop', '~> 0.52.1'
gem 'coveralls', '~> 0.8.2', require: false gem 'coveralls', '~> 0.8.23', require: false
end end

102
README.md
Просмотреть файл

@@ -2,9 +2,7 @@
[![Gem Version](https://badge.fury.io/rb/telegram-bot.svg)](http://badge.fury.io/rb/telegram-bot) [![Gem Version](https://badge.fury.io/rb/telegram-bot.svg)](http://badge.fury.io/rb/telegram-bot)
[![Code Climate](https://codeclimate.com/github/telegram-bot-rb/telegram-bot/badges/gpa.svg)](https://codeclimate.com/github/telegram-bot-rb/telegram-bot) [![Code Climate](https://codeclimate.com/github/telegram-bot-rb/telegram-bot/badges/gpa.svg)](https://codeclimate.com/github/telegram-bot-rb/telegram-bot)
[![Build Status](https://travis-ci.org/telegram-bot-rb/telegram-bot.svg)](https://travis-ci.org/telegram-bot-rb/telegram-bot) [![Build Status](https://travis-ci.com/telegram-bot-rb/telegram-bot.svg)](https://travis-ci.com/telegram-bot-rb/telegram-bot)
__Breaking changes in v0.14!__ See [upgrade guide](https://github.com/telegram-bot-rb/telegram-bot/wiki/Upgrading-to-0.14).
Tools for developing Telegram bots. Best used with Rails, but can be used in Tools for developing Telegram bots. Best used with Rails, but can be used in
[standalone app](https://github.com/telegram-bot-rb/telegram-bot/wiki/Not-rails-application). [standalone app](https://github.com/telegram-bot-rb/telegram-bot/wiki/Not-rails-application).
@@ -27,9 +25,33 @@ with session, keyboards and inline queries.
Run it on your local machine in 1 minute! Run it on your local machine in 1 minute!
And here is [app template](https://github.com/telegram-bot-rb/rails_template) And here is [app template](https://github.com/telegram-bot-rb/rails_template)
to generate clean app in seconds. to generate new rails app in seconds.
Examples and cookbook in [wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki). See examples and cookbook in [the wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki).
## Table of Contents
* [Installation](#installation)
* [Usage](#usage)
* [Configuration](#configuration)
* [Configuration in Rails app](#configuration-in-rails-app)
* [Client](#client)
* [Typed responses](#typed-responses)
* [Controller](#controller)
* [Reply helpers](#reply-helpers)
* [Optional typecasting](#optional-typecasting)
* [Session](#session)
* [Message context](#message-context)
* [Callback queries](#callback-queries)
* [Routes in Rails app](#routes-in-rails-app)
* [Processing updates](#processing-updates)
* [Development & Debugging](#development--debugging)
* [Testing](#testing)
* [Deployment](#deployment)
* [Async mode](#async-mode)
* [Limitations](#limitations)
* [Development](#development)
* [Contributing](#contributing)
## Installation ## Installation
@@ -41,11 +63,15 @@ gem 'telegram-bot'
And then execute: And then execute:
$ bundle ```
bundle
```
Or install it yourself as: Or install it yourself as:
$ gem install telegram-bot ```
gem install telegram-bot
```
Require if necessary: Require if necessary:
@@ -65,7 +91,11 @@ which is used for `Telegram.bot`.
```ruby ```ruby
Telegram.bots_config = { Telegram.bots_config = {
default: DEFAULT_BOT_TOKEN, default: DEFAULT_BOT_TOKEN,
chat: {token: CHAT_BOT_TOKEN, username: 'chatbot'}, 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.get_updates
@@ -87,6 +117,7 @@ development:
bot: bot:
token: TOKEN token: TOKEN
username: SomeBot username: SomeBot
server: http://local.bot.api.server
# For multiple bots in single app use hash of `internal_bot_id => settings` # For multiple bots in single app use hash of `internal_bot_id => settings`
bots: bots:
@@ -100,10 +131,12 @@ development:
For Rails >= 5.2 `Telegram::Bot` searches for config first in credentials and then in secrets. 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 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 may not load `rails credentials:edit`. In this case be aware of that [Rails < 6.0 may not load
credentials in dev environment by default](https://github.com/telegram-bot-rb/telegram-bot/issues/74#issuecomment-384205609). credentials in dev environment by default](https://github.com/telegram-bot-rb/telegram-bot/issues/74#issuecomment-384205609).
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 sesitive data 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 and it's more difficult to use in multiple environments. See
[secure_credentials](https://github.com/printercu/secure_credentials) gem for better option. [secure_credentials](https://github.com/printercu/secure_credentials) gem for better option.
@@ -126,9 +159,9 @@ bot.send_message(chat_id: chat_id, text: 'Test')
``` ```
There is no magic, they just pass params as is and set `path_suffix`. There is no magic, they just pass params as is and set `path_suffix`.
See [`Client`](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/client.rb) See [list of available methods](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/client/api_methods.txt)
class for list of available methods. Please open PR or issue if it misses methods from fetched from [API docs](https://core.telegram.org/bots/api).
new API versions. 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. 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. Special `Telegram::Bot::Forbidden` is raised when bot can't post messages to the chat anymore.
@@ -165,19 +198,18 @@ Bot controllers like usual rails controllers provides features like callbacks,
```ruby ```ruby
class Telegram::WebhookController < Telegram::Bot::UpdatesController class Telegram::WebhookController < Telegram::Bot::UpdatesController
# use callbacks like in any other controllers # use callbacks like in any other controller
around_action :with_locale around_action :with_locale
# Every update can have one of: message, inline_query, chosen_inline_result, # Every update has one of: message, inline_query, chosen_inline_result,
# callback_query, etc. # callback_query, etc.
# Define method with same name to respond to this updates. # Define method with the same name to handle this type of update.
def message(message) def message(message)
# message can be also accessed via instance method
message == self.payload # true
# store_message(message['text']) # store_message(message['text'])
end end
# This basic methods receives commonly used params: # For the following types of updates commonly used params are passed as arguments,
# full payload object is available with `payload` instance method.
# #
# message(payload) # message(payload)
# inline_query(query, offset) # inline_query(query, offset)
@@ -188,14 +220,19 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
# Command arguments will be parsed and passed to the method. # Command arguments will be parsed and passed to the method.
# Be sure to use splat args and default values to not get errors when # Be sure to use splat args and default values to not get errors when
# someone passed more or less arguments in the message. # someone passed more or less arguments in the message.
def start!(data = nil, *) def start!(word = nil, *other_words)
# do_smth_with(data) # 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. # There are `chat` & `from` shortcut methods.
# For callback queries `chat` if taken from `message` when it's available. # For callback queries `chat` is taken from `message` when it's available.
response = from ? "Hello #{from['username']}!" : 'Hi there!' response = from ? "Hello #{from['username']}!" : 'Hi there!'
# There is `respond_with` helper to set `chat_id` from received message: # There is `respond_with` helper to set `chat_id` from received message:
respond_with :message, text: response respond_with :message, text: response
# `reply_with` also sets `reply_to_message_id`: # `reply_with` also sets `reply_to_message_id`:
reply_with :photo, photo: File.open('party.jpg') reply_with :photo, photo: File.open('party.jpg')
end end
@@ -218,10 +255,10 @@ end
#### Reply helpers #### Reply helpers
There are helpers to respond for basic actions. They just set chat/message/query There are helpers for basic responses. They just set chat/message/query
identifiers from update. See identifiers from the update. See
[`ReplyHelpers`](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/updates_controller/reply_helpers.rb) [`ReplyHelpers`](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/updates_controller/reply_helpers.rb)
module for more information. Here are this methods signatures: module for more information. Here are these methods signatures:
```ruby ```ruby
def respond_with(type, params); end def respond_with(type, params); end
@@ -229,6 +266,8 @@ def reply_with(type, params); end
def answer_inline_query(results, params = {}); end def answer_inline_query(results, params = {}); end
def answer_callback_query(text, params = {}); end def answer_callback_query(text, params = {}); end
def edit_message(type, 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 #### Optional typecasting
@@ -362,7 +401,7 @@ end
### Routes in Rails app ### Routes in Rails app
There is `telegram_webhook` helper for rails app to define routes for webhooks. There is `telegram_webhook` helper for rails app to define routes for webhooks.
It defines routes at `telegram/#{bot.token}` and connects bots with controller. It defines routes at `telegram/#{hash_of(bot.token)}` and connects bots with controller.
```ruby ```ruby
# Most off apps would require # Most off apps would require
@@ -382,7 +421,7 @@ telegram_webhook TelegramAuctionController, :auction
telegram_webhook TelegramController, as: :custom_telegram_webhook telegram_webhook TelegramController, as: :custom_telegram_webhook
``` ```
#### Processesing updates #### Processing updates
To process update with controller call `.dispatch(bot, update)` on it. To process update with controller call `.dispatch(bot, update)` on it.
There are several options to run it automatically: There are several options to run it automatically:
@@ -471,10 +510,11 @@ RSpec.describe TelegramWebhooksController, telegram_bot: :rails do
# dispatch_command(cmd, *args) # dispatch_command(cmd, *args)
# Available matchers can be found in Telegram::Bot::RSpec::ClientMatchers. # Available matchers can be found in Telegram::Bot::RSpec::ClientMatchers.
it 'shows usage of basic matchers' it 'shows usage of basic matchers' do
# The most basic one is #make_telegram_request(bot, endpoint, params_matcher) # The most basic one is #make_telegram_request(bot, action).
expect { dispatch_command(:start) }. # It works similar to `receive` matcher and supports chaining `.with(args).exactly(n).times`.
to make_telegram_request(bot, :sendMessage, hash_including(text: 'msg text')) 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. # There are some shortcuts for dispatching basic updates and testing responses.
expect { dispatch_message('Hi') }.to send_telegram_message(bot, /msg regexp/, some: :option) expect { dispatch_message('Hi') }.to send_telegram_message(bot, /msg regexp/, some: :option)

Просмотреть файл

@@ -25,7 +25,7 @@ method_list = headers.
map { |g| g.reject { |x| x.match?(NOT_METHOD_REGEXP) } }. map { |g| g.reject { |x| x.match?(NOT_METHOD_REGEXP) } }.
reject(&:empty?) reject(&:empty?)
api_version = doc.text.match(/^(Bot API ([\d\.]+))\.?$/) api_version = doc.text.match(/^(?:Introducing )?(Bot API ([\d\.]+))\.?$/)
result = ['# Generated with bin/fetch-telegram-methods'] result = ['# Generated with bin/fetch-telegram-methods']
result << "# #{api_version[1]}" if api_version result << "# #{api_version[1]}" if api_version

Просмотреть файл

@@ -2,20 +2,20 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "actionpack", "~> 4.2" gem "actionpack", "~> 4.2.0"
gem "railties", "~> 4.2" gem "railties", "~> 4.2.0"
group :development do group :development do
gem "appraisal", "~> 2.2" gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1" gem "pry", "~> 0.13.1"
gem "pry-byebug", "~> 3.2.0" gem "pry-byebug", "~> 3.9.0"
gem "sdoc", "~> 0.4.1" gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.0" gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0" gem "rspec", "~> 3.10.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 3.5.0" gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1" gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

Просмотреть файл

@@ -2,20 +2,20 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "actionpack", "~> 5.0" gem "actionpack", "~> 5.0.0"
gem "railties", "~> 5.0" gem "railties", "~> 5.0.0"
group :development do group :development do
gem "appraisal", "~> 2.2" gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1" gem "pry", "~> 0.13.1"
gem "pry-byebug", "~> 3.2.0" gem "pry-byebug", "~> 3.9.0"
gem "sdoc", "~> 0.4.1" gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.0" gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0" gem "rspec", "~> 3.10.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 3.5.0" gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1" gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

Просмотреть файл

@@ -2,20 +2,20 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "actionpack", "~> 5.1" gem "actionpack", "~> 5.1.0"
gem "railties", "~> 5.1" gem "railties", "~> 5.1.0"
group :development do group :development do
gem "appraisal", "~> 2.2" gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1" gem "pry", "~> 0.13.1"
gem "pry-byebug", "~> 3.2.0" gem "pry-byebug", "~> 3.9.0"
gem "sdoc", "~> 0.4.1" gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.0" gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0" gem "rspec", "~> 3.10.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 3.5.0" gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1" gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

Просмотреть файл

@@ -2,20 +2,20 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "actionpack", "~> 5.2.0.beta2" gem "actionpack", "~> 5.2.0"
gem "railties", "~> 5.2.0.beta2" gem "railties", "~> 5.2.0"
group :development do group :development do
gem "appraisal", "~> 2.2" gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1" gem "pry", "~> 0.13.1"
gem "pry-byebug", "~> 3.2.0" gem "pry-byebug", "~> 3.9.0"
gem "sdoc", "~> 0.4.1" gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.0" gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0" gem "rspec", "~> 3.10.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 3.5.0" gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1" gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

Просмотреть файл

@@ -2,22 +2,20 @@
source "https://rubygems.org" source "https://rubygems.org"
git "https://github.com/rails/rails.git" do gem "actionpack", "~> 6.0.0"
gem "actionpack", "~> 6.0.0.alpha" gem "railties", "~> 6.0.0"
gem "railties", "~> 6.0.0.alpha"
end
group :development do group :development do
gem "appraisal", "~> 2.2" gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1" gem "pry", "~> 0.13.1"
gem "pry-byebug", "~> 3.2.0" gem "pry-byebug", "~> 3.9.0"
gem "sdoc", "~> 0.4.1" gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.0" gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0" gem "rspec", "~> 3.10.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 3.5.0" gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1" gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

21
gemfiles/rails_61.gemfile Обычный файл
Просмотреть файл

@@ -0,0 +1,21 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 6.1.0"
gem "railties", "~> 6.1.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.13.1"
gem "pry-byebug", "~> 3.9.0"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.10.0"
gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.23", require: false
end
gemspec path: "../"

Просмотреть файл

@@ -14,15 +14,22 @@ namespace :telegram do
desc 'Set webhook urls for all bots' desc 'Set webhook urls for all bots'
task set_webhook: :environment do task set_webhook: :environment do
routes = Rails.application.routes.url_helpers Telegram::Bot::Tasks.set_webhook
cert_file = ENV['CERT'] end
cert = File.open(cert_file) if cert_file
Telegram.bots.each do |key, bot| desc 'Delete webhooks for all or specific BOT'
route_name = Telegram::Bot::RoutesHelper.route_name_for_bot(bot) task :delete_webhook do
url = routes.send("#{route_name}_url") Telegram::Bot::Tasks.delete_webhook
puts "Setting webhook for #{key}..." end
bot.async(false) { bot.set_webhook(url: url, certificate: cert) }
end desc 'Perform logOut command for all or specific BOT'
task :log_out do
Telegram::Bot::Tasks.log_out
end
desc 'Perform `close` command for all or specific BOT'
task :close do
Telegram::Bot::Tasks.close
end end
end end
end end

Просмотреть файл

@@ -14,10 +14,10 @@ module Telegram
module_function module_function
def deprecation_0_15 def deprecation_0_16
@deprecation ||= begin @deprecation ||= begin
require 'active_support/deprecation' require 'active_support/deprecation'
ActiveSupport::Deprecation.new('0.15', 'Telegram::Bot') ActiveSupport::Deprecation.new('0.16', 'Telegram::Bot')
end end
end end
@@ -25,9 +25,9 @@ module Telegram
autoload :Client, 'telegram/bot/client' autoload :Client, 'telegram/bot/client'
autoload :ClientStub, 'telegram/bot/client_stub' autoload :ClientStub, 'telegram/bot/client_stub'
autoload :DebugClient, 'telegram/bot/debug_client' autoload :DebugClient, 'telegram/bot/debug_client'
autoload :Initializers, 'telegram/bot/initializers'
autoload :Middleware, 'telegram/bot/middleware' autoload :Middleware, 'telegram/bot/middleware'
autoload :RSpec, 'telegram/bot/rspec' autoload :RSpec, 'telegram/bot/rspec'
autoload :Tasks, 'telegram/bot/tasks'
autoload :UpdatesController, 'telegram/bot/updates_controller' autoload :UpdatesController, 'telegram/bot/updates_controller'
autoload :UpdatesPoller, 'telegram/bot/updates_poller' autoload :UpdatesPoller, 'telegram/bot/updates_poller'
end end

Просмотреть файл

@@ -16,8 +16,8 @@ module Telegram
# accessible via `Teletgram.bots[id]` in job worker. Or just use # accessible via `Teletgram.bots[id]` in job worker. Or just use
# `Telegram.bots_config=` for configuration. # `Telegram.bots_config=` for configuration.
# #
# Being in async mode `#request` enqueues job instead to perform # Being in async mode `#request` enqueues job to perform
# http request instead of performing it directly. # http request instead of performing it immediately.
# Async behavior is controlled with `#async=` writer # Async behavior is controlled with `#async=` writer
# and can be enabled/disabled for the block with `#async`: # and can be enabled/disabled for the block with `#async`:
# #
@@ -29,7 +29,7 @@ module Telegram
# while `#async(val, &block)` is thread-safe. # while `#async(val, &block)` is thread-safe.
# #
# It can be set with custom job class or classname. By default it defines # It can be set with custom job class or classname. By default it defines
# job classes for every client class, inherited from ApplicationRecord, which # job classes inherited from ApplicationJob, which
# can be accessed via `.default_async_job`. You can integrate it with any # can be accessed via `.default_async_job`. You can integrate it with any
# other job provider by defining a class with `.perform_later(bot_id, *args)` # other job provider by defining a class with `.perform_later(bot_id, *args)`
# method. See Async::Job for implemetation. # method. See Async::Job for implemetation.

Просмотреть файл

@@ -1,13 +1,14 @@
require 'active_support/core_ext/hash/keys'
require 'json' require 'json'
require 'httpclient' require 'httpclient'
module Telegram module Telegram
module Bot module Bot
class Client class Client
URL_TEMPLATE = 'https://api.telegram.org/bot%<token>s/'.freeze SERVER = 'https://api.telegram.org'.freeze
URL_TEMPLATE = '%<server>s/bot%<token>s/'.freeze
autoload :TypedResponse, 'telegram/bot/client/typed_response' autoload :TypedResponse, 'telegram/bot/client/typed_response'
extend Initializers
prepend Async prepend Async
include DebugClient include DebugClient
@@ -15,6 +16,16 @@ module Telegram
include ApiHelper include ApiHelper
class << self class << self
# Accepts different options to initialize bot.
def wrap(input, **options)
case input
when Symbol then by_id(input) or raise "#{name} #{input.inspect} not configured"
when self then input
when Hash then new(**input.symbolize_keys, **options)
else new(input, **options)
end
end
def by_id(id) def by_id(id)
Telegram.bots[id] Telegram.bots[id]
end end
@@ -51,11 +62,11 @@ module Telegram
attr_reader :client, :token, :username, :base_uri attr_reader :client, :token, :username, :base_uri
def initialize(token = nil, username = nil, **options) def initialize(token = nil, username = nil, server: SERVER, **options)
@client = HTTPClient.new @client = HTTPClient.new
@token = token || options[:token] @token = token || options[:token]
@username = username || options[:username] @username = username || options[:username]
@base_uri = format(URL_TEMPLATE, token: self.token) @base_uri = format(URL_TEMPLATE, server: server, token: self.token)
end end
def request(action, body = {}) def request(action, body = {})

Просмотреть файл

@@ -1,5 +1,5 @@
# Generated with bin/fetch-telegram-methods # Generated with bin/fetch-telegram-methods
# Bot API 4.1 # Bot API 5.3
getUpdates getUpdates
setWebhook setWebhook
@@ -7,8 +7,11 @@ deleteWebhook
getWebhookInfo getWebhookInfo
getMe getMe
logOut
close
sendMessage sendMessage
forwardMessage forwardMessage
copyMessage
sendPhoto sendPhoto
sendAudio sendAudio
sendDocument sendDocument
@@ -22,33 +25,45 @@ editMessageLiveLocation
stopMessageLiveLocation stopMessageLiveLocation
sendVenue sendVenue
sendContact sendContact
sendPoll
sendDice
sendChatAction sendChatAction
getUserProfilePhotos getUserProfilePhotos
getFile getFile
kickChatMember banChatMember
unbanChatMember unbanChatMember
restrictChatMember restrictChatMember
promoteChatMember promoteChatMember
setChatAdministratorCustomTitle
setChatPermissions
exportChatInviteLink exportChatInviteLink
createChatInviteLink
editChatInviteLink
revokeChatInviteLink
setChatPhoto setChatPhoto
deleteChatPhoto deleteChatPhoto
setChatTitle setChatTitle
setChatDescription setChatDescription
pinChatMessage pinChatMessage
unpinChatMessage unpinChatMessage
unpinAllChatMessages
leaveChat leaveChat
getChat getChat
getChatAdministrators getChatAdministrators
getChatMembersCount getChatMemberCount
getChatMember getChatMember
setChatStickerSet setChatStickerSet
deleteChatStickerSet deleteChatStickerSet
answerCallbackQuery answerCallbackQuery
setMyCommands
deleteMyCommands
getMyCommands
editMessageText editMessageText
editMessageCaption editMessageCaption
editMessageMedia editMessageMedia
editMessageReplyMarkup editMessageReplyMarkup
stopPoll
deleteMessage deleteMessage
sendSticker sendSticker
@@ -58,6 +73,7 @@ createNewStickerSet
addStickerToSet addStickerToSet
setStickerPositionInSet setStickerPositionInSet
deleteStickerFromSet deleteStickerFromSet
setStickerSetThumb
answerInlineQuery answerInlineQuery

Просмотреть файл

@@ -5,11 +5,11 @@ module Telegram
attr_reader :requests attr_reader :requests
module StubbedConstructor module StubbedConstructor
def new(*args) def new(*args, **kwargs)
if self == ClientStub || !ClientStub.stub_all? if self == ClientStub || !ClientStub.stub_all?
super super
else else
ClientStub.new(*args) ClientStub.new(*args, **kwargs)
end end
end end
end end

Просмотреть файл

@@ -1,21 +0,0 @@
require 'active_support/core_ext/hash/keys'
module Telegram
module Bot
module Initializers
# Accepts different options to initialize bot.
def wrap(input, **options)
case input
when Symbol then by_id(input) or raise "#{name} #{input.inspect} not configured"
when self then input
when Hash then new(**input.symbolize_keys, **options)
else new(input, **options)
end
end
def by_id(_id)
raise 'Not implemented'
end
end
end
end

Просмотреть файл

@@ -1,3 +1,6 @@
require 'base64'
require 'openssl'
require 'telegram/bot' require 'telegram/bot'
require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/array/wrap'
@@ -17,10 +20,8 @@ module Telegram
end || 'telegram_webhook' end || 'telegram_webhook'
end end
# Replaces colon with underscore so rails don't treat it as def token_hash(token)
# route parameter. Base64.urlsafe_encode64(OpenSSL::Digest::SHA1.digest(token), padding: false)
def escape_token(token)
token && token.tr(':', '_')
end end
end end
@@ -34,14 +35,17 @@ module Telegram
# #
# # pass additional options # # pass additional options
# telegram_webhook TelegramController, :default, as: :custom_route_name # telegram_webhook TelegramController, :default, as: :custom_route_name
def telegram_webhook(controller, bot = :default, **options) #
# # Default path is generated using hashed bot token. Override it using:
# telegram_webhook TelegramController, :default, path: 'top/secret'
def telegram_webhook(controller, bot = :default, path: nil, **options)
bot = Client.wrap(bot) bot = Client.wrap(bot)
params = { params = {
to: Middleware.new(bot, controller), to: Middleware.new(bot, controller),
as: RoutesHelper.route_name_for_bot(bot), as: RoutesHelper.route_name_for_bot(bot),
format: false, format: false,
}.merge!(options) }.merge!(options)
post("telegram/#{RoutesHelper.escape_token bot.token}", params) post(path || "telegram/#{bot.token && RoutesHelper.token_hash(bot.token)}", params)
UpdatesPoller.add(bot, controller) if Telegram.bot_poller_mode? UpdatesPoller.add(bot, controller) if Telegram.bot_poller_mode?
end end
end end

Просмотреть файл

@@ -1,10 +0,0 @@
require 'telegram/bot'
Telegram::Bot.deprecation_0_15.warn(
"`require 'telegram/bot/rspec/integration'` is deprecated in favor of " \
"`require 'telegram/bot/rspec/integration/rails'`"
)
require 'telegram/bot/rspec/integration/rails'
Telegram::Bot::RSpec.with_include_context do |config|
config.include_context 'telegram/bot/integration/rails', telegram_bot: true
end

63
lib/telegram/bot/tasks.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
# frozen_string_literal: true
module Telegram
module Bot
module Tasks
extend self
def set_webhook
routes = Rails.application.routes.url_helpers
cert_file = ENV['CERT']
cert = File.open(cert_file) if cert_file
each_bot do |key, bot|
route_name = RoutesHelper.route_name_for_bot(bot)
url = routes.send("#{route_name}_url")
say("Setting webhook for #{key}...")
bot.set_webhook(
url: url,
certificate: cert,
ip_address: ENV['IP_ADDRESS'],
drop_pending_updates: drop_pending_updates,
)
end
end
def delete_webhook
each_bot do |key, bot|
say("Deleting webhook for #{key}...")
bot.delete_webhook(drop_pending_updates: drop_pending_updates)
end
end
def log_out
each_bot do |key, bot|
say("Logging out #{key}...")
bot.log_out
end
end
def close
each_bot do |key, bot|
say("Closing #{key}...")
bot.close
end
end
private
def say(text)
puts(text) unless Rails.env.test? # rubocop:disable Rails/Output
end
def each_bot(&block)
id = ENV['BOT'].try!(:to_sym)
bots = id ? {id => Client.by_id(id)} : Telegram.bots
bots.each { |key, bot| bot.async(false) { block[key, bot] } }
end
def drop_pending_updates
ENV['DROP_PENDING_UPDATES'].try!(:downcase) == 'true'
end
end
end
end

Просмотреть файл

@@ -7,13 +7,13 @@ module Telegram
module Bot module Bot
# Base class to create update processors. With callbacks, session and helpers. # Base class to create update processors. With callbacks, session and helpers.
# #
# Define public methods for each command and they will be called when # Public methods ending with `!` handle messages with commands.
# update has this command. Message is automatically parsed and # Message text is automatically parsed into method arguments.
# words are passed as method arguments. Be sure to use default values and # Be sure to use default values and
# splat arguments in every action method to not get errors, when user # splat arguments in every action method to not get errors, when user
# sends command without necessary args / with extra args. # sends command without necessary args / with extra args.
# #
# def start(token = nil, *) # def start!(token = nil, *)
# if token # if token
# # ... # # ...
# else # else
@@ -21,25 +21,21 @@ module Telegram
# end # end
# end # end
# #
# def help(*) # def help!(*)
# respond_with :message, text: # respond_with :message, text:
# end # end
# #
# To process plain text messages (without commands) or other updates just # To process plain text messages (without commands) or other updates just
# define public method with name of payload type. They will receive payload # define public method with name of payload type.
# as an argument. # By default they receive payload as an argument, but some of them are called
# with more usefuk args:
# #
# def message(message) # def message(message)
# respond_with :message, text: "Echo: #{message['text']}" # respond_with :message, text: "Echo: #{message['text']}"
# end # end
# #
# def inline_query(query) # def inline_query(query, offset)
# answer_inline_query results_for_query(query), is_personal: true # answer_inline_query results_for_query(query, offset), is_personal: true
# end
#
# # To process conflicting commands (`/message args`) just use `on_` prefix:
# def on_message(*args)
# # ...
# end # end
# #
# To process update run: # To process update run:
@@ -101,6 +97,10 @@ module Telegram
callback_query callback_query
shipping_query shipping_query
pre_checkout_query pre_checkout_query
poll
poll_answer
my_chat_member
chat_member
].freeze ].freeze
class << self class << self
@@ -152,7 +152,7 @@ module Telegram
# Accessor to `'from'` field of payload. Can be overriden with `from` option # Accessor to `'from'` field of payload. Can be overriden with `from` option
# for #initialize. # for #initialize.
def from def from
@_from ||= payload && payload['from'] @_from ||= payload.is_a?(Hash) ? payload['from'] : payload.try(:from)
end end
# Processes current update. # Processes current update.
@@ -210,6 +210,10 @@ module Telegram
[payload_type, [payload['data']]] [payload_type, [payload['data']]]
end end
def action_for_poll_answer
[payload_type, [payload['poll_id'], payload['option_ids']]]
end
# Silently ignore unsupported messages to not fail when user crafts # Silently ignore unsupported messages to not fail when user crafts
# an update with usupported command, callback query context, etc. # an update with usupported command, callback query context, etc.
def action_missing(action, *_args) def action_missing(action, *_args)

Просмотреть файл

@@ -49,7 +49,7 @@ module Telegram
private private
# A hook invoked every time a before callback is halted. # A hook invoked every time a before callback is halted.
def halted_callback_hook(filter) def halted_callback_hook(filter, *)
Instrumentation.instrument(:halted_callback, filter: filter) Instrumentation.instrument(:halted_callback, filter: filter)
end end

Просмотреть файл

@@ -21,7 +21,7 @@ module Telegram
respond_with(type, params) respond_with(type, params)
end end
# Same as reply_with, but for inline queries. # Same as respond_with, but for inline queries.
def answer_inline_query(results, params = {}) def answer_inline_query(results, params = {})
params = params.merge( params = params.merge(
inline_query_id: payload['id'], inline_query_id: payload['id'],
@@ -30,7 +30,7 @@ module Telegram
bot.answer_inline_query(params) bot.answer_inline_query(params)
end end
# Same as reply_with, but for callback queries. # Same as respond_with, but for callback queries.
def answer_callback_query(text, params = {}) def answer_callback_query(text, params = {})
params = params.merge( params = params.merge(
callback_query_id: payload['id'], callback_query_id: payload['id'],
@@ -39,6 +39,23 @@ module Telegram
bot.answer_callback_query(params) bot.answer_callback_query(params)
end end
# Same as respond_with, but for pre checkout queries.
def answer_pre_checkout_query(ok, params = {})
params = params.merge(
pre_checkout_query_id: payload['id'],
ok: ok,
)
bot.answer_pre_checkout_query(params)
end
def answer_shipping_query(ok, params = {})
params = params.merge(
shipping_query_id: payload['id'],
ok: ok,
)
bot.answer_shipping_query(params)
end
# Edit message from callback query. # Edit message from callback query.
def edit_message(type, params = {}) def edit_message(type, params = {})
params = params =

Просмотреть файл

@@ -12,15 +12,15 @@ module Telegram
module ClassMethods module ClassMethods
# Class-level helper for lazy translations. # Class-level helper for lazy translations.
def translate(key, options = {}) def translate(key, **options)
key = "#{controller_path.tr('/', '.')}#{key}" if key.to_s.start_with?('.') key = "#{controller_path.tr('/', '.')}#{key}" if key.to_s.start_with?('.')
I18n.translate(key, options) I18n.translate(key, **options)
end end
alias :t :translate alias :t :translate
end end
# See toplevel description. # See toplevel description.
def translate(key, options = {}) def translate(key, **options)
if key.to_s.start_with?('.') if key.to_s.start_with?('.')
path = controller_path.tr('/', '.') path = controller_path.tr('/', '.')
defaults = [:"#{path}#{key}"] defaults = [:"#{path}#{key}"]
@@ -28,7 +28,7 @@ module Telegram
options[:default] = defaults.flatten options[:default] = defaults.flatten
key = "#{path}.#{action_name_i18n_key}#{key}" key = "#{path}.#{action_name_i18n_key}#{key}"
end end
I18n.translate(key, options) I18n.translate(key, **options)
end end
alias :t :translate alias :t :translate

Просмотреть файл

@@ -50,7 +50,7 @@ module Telegram
ensure ensure
@running = false @running = false
end end
log { 'Stoped polling bot updates.' } log { 'Stopped polling bot updates.' }
end end
def run def run

Просмотреть файл

@@ -1,6 +1,6 @@
module Telegram module Telegram
module Bot module Bot
VERSION = '0.14.1'.freeze VERSION = '0.15.4'.freeze
def self.gem_version def self.gem_version
Gem::Version.new VERSION Gem::Version.new VERSION

74
spec/integration/tasks_spec.rb Обычный файл
Просмотреть файл

@@ -0,0 +1,74 @@
# frozen_string_literal: true
require 'integration_helper'
RSpec.describe Telegram::Bot::Tasks, type: :request do
include Telegram::Bot::RSpec::ClientMatchers
def stub_env(new_values)
new_values.stringify_keys!
old_values = ENV.to_h.slice(new_values.keys)
new_values.each { |k, v| ENV[k] = v.to_s }
yield
ensure
new_values.each_key { |k| ENV[k] = old_values[k] }
end
shared_examples 'uses BOT envar' do |bot_matcher|
it 'runs for all bots by default' do
matcher = Telegram.bots.values.map { |x| instance_exec(x, &bot_matcher) }.reduce(&:and)
expect(subject).to matcher
end
context 'when BOT specified' do
around { |ex| stub_env(BOT: :other) { ex.run } }
it 'runs for this bot only' do
expect(subject).to instance_exec(Telegram.bots[:other], &bot_matcher)
end
end
end
describe '#log_out' do
subject { -> { described_class.log_out } }
include_examples 'uses BOT envar', ->(bot) { make_telegram_request(bot, :logOut) }
end
describe '#close' do
subject { -> { described_class.close } }
include_examples 'uses BOT envar', ->(bot) { make_telegram_request(bot, :close) }
end
describe '#set_webhook' do
subject { -> { described_class.set_webhook } }
include_examples 'uses BOT envar', ->(bot) { make_telegram_request(bot, :setWebhook) }
context 'with options in env' do
around do |ex|
stub_env(IP_ADDRESS: '1.2.3.4', DROP_PENDING_UPDATES: 'TrUe') do
ex.run
end
end
include_examples 'uses BOT envar', ->(bot) {
make_telegram_request(bot, :setWebhook).with(hash_including(
ip_address: '1.2.3.4',
drop_pending_updates: true,
))
}
end
end
describe '#delete_webhook' do
subject { -> { described_class.delete_webhook } }
include_examples 'uses BOT envar', ->(bot) { make_telegram_request(bot, :deleteWebhook) }
context 'with options in env' do
around { |ex| stub_env(DROP_PENDING_UPDATES: 'TrUe') { ex.run } }
include_examples 'uses BOT envar', ->(bot) {
make_telegram_request(bot, :deleteWebhook).with(hash_including(
drop_pending_updates: true,
))
}
end
end
end

Просмотреть файл

@@ -14,6 +14,7 @@ class TestApplication < Rails::Application
config.eager_load = false config.eager_load = false
config.log_level = :debug config.log_level = :debug
config.action_dispatch.show_exceptions = false config.action_dispatch.show_exceptions = false
routes.default_url_options = {host: 'test.rpsec'}
telegram_config = { telegram_config = {
bot: 'default_token', bot: 'default_token',

Просмотреть файл

@@ -1,4 +1,4 @@
RSpec.shared_examples 'async' do |request_args: -> {}| RSpec.shared_examples 'async' do |request_args:|
let(:instance) { described_class.new(token: token, id: id, async: async) } let(:instance) { described_class.new(token: token, id: id, async: async) }
let(:id) { :default_bot } let(:id) { :default_bot }
let(:async) { true } let(:async) { true }
@@ -103,7 +103,7 @@ RSpec.shared_examples 'async' do |request_args: -> {}|
end end
describe '#request' do describe '#request' do
subject { ->(*args) { instance.request(*(args.empty? ? self.args : args)) } } subject { ->(*args) { instance.request(*args) } }
let(:args, &request_args) let(:args, &request_args)
let(:result) { double(:result) } let(:result) { double(:result) }
@@ -112,7 +112,7 @@ RSpec.shared_examples 'async' do |request_args: -> {}|
expect(instance).to_not receive(:http_request) expect(instance).to_not receive(:http_request)
expect(described_class).to receive(:prepare_async_args).with(*args) { args } expect(described_class).to receive(:prepare_async_args).with(*args) { args }
expect(instance.async).to receive(:perform_later).with(id.to_s, *args) { result } expect(instance.async).to receive(:perform_later).with(id.to_s, *args) { result }
expect(subject.call).to eq result expect(subject.call(*args)).to eq result
end end
end end
@@ -131,12 +131,11 @@ RSpec.shared_examples 'async' do |request_args: -> {}|
context 'when async is disabled' do context 'when async is disabled' do
let(:async) { false } let(:async) { false }
let(:result) { double(status: 200, body: '{"test":"ok"}') } let(:result) { double(status: 200, body: '{"test":"ok"}') }
let(:args, &request_args)
it 'performs request immediately' do it 'performs request immediately' do
expect(instance).to receive(:request).with(*args).and_call_original expect(instance).to receive(:request).with(*args).and_call_original
expect(instance).to receive(:http_request) { result } expect(instance).to receive(:http_request) { result }
expect(subject[]).to eq 'test' => 'ok' expect(subject.call(*args)).to eq 'test' => 'ok'
end end
end end
end end

Просмотреть файл

@@ -1,65 +0,0 @@
RSpec.shared_examples 'initializers' do |config_method = :bots|
describe '.wrap' do
subject { described_class.wrap(input, **options) }
let(:options) { {} }
let(:result) { double(:result) }
let(:username) { 'username' }
context 'when input is a string' do
let(:input) { token }
it 'treats string as token' do
expect(described_class).to receive(:new).with(token, {}) { result }
should eq result
end
context 'and additional options are given' do
let(:options) { {id: :test} }
it 'passes them to initializer' do
expect(described_class).to receive(:new).with(input, **options) { result }
should eq result
end
end
end
context 'when input is a hash' do
let(:input) { {token: token, 'username' => username, other: :options} }
it 'passes it with symbolized keys' do
expect(described_class).to receive(:new).with(**input.symbolize_keys) { result }
should eq result
end
context 'and additional options are given' do
let(:options) { {id: :test} }
it 'passes them to initializer' do
expect(described_class).to receive(:new).
with(**input.symbolize_keys, **options) { result }
should eq result
end
end
end
context 'when input is an instance of described_class' do
let!(:input) { instance }
it 'returns input' do
expect(described_class).to_not receive(:new)
should eq input
end
end
context 'when input is a Symbol' do
let(:input) { :client_1 }
before { allow(Telegram).to receive(config_method) { {client_1: instance} } }
it { should eq Telegram.send(config_method)[:client_1] }
context 'and there is no such bot' do
let(:input) { :invalid }
it { expect { subject }.to raise_error(/not configured/) }
end
end
end
end

Просмотреть файл

@@ -2,9 +2,75 @@ RSpec.describe Telegram::Bot::Client do
let(:instance) { described_class.new 'token' } let(:instance) { described_class.new 'token' }
let(:token) { 'token' } let(:token) { 'token' }
include_examples 'initializers'
it_behaves_like 'async', request_args: -> { [double(:action), {body: :content}] } it_behaves_like 'async', request_args: -> { [double(:action), {body: :content}] }
describe '.wrap' do
subject { described_class.wrap(input, **options) }
let(:options) { {} }
let(:result) { double(:result) }
let(:username) { 'username' }
context 'when input is a string' do
let(:input) { token }
it 'treats string as token' do
expect(described_class).to receive(:new) do |*args, **kwargs|
expect([*args, kwargs]).to eq([token, {}]) # not using .with to support ruby 2.x and 3.x
result
end
should eq result
end
context 'and additional options are given' do
let(:options) { {id: :test} }
it 'passes them to initializer' do
expect(described_class).to receive(:new).with(input, **options) { result }
should eq result
end
end
end
context 'when input is a hash' do
let(:input) { {token: token, 'username' => username, other: :options} }
it 'passes it with symbolized keys' do
expect(described_class).to receive(:new).with(**input.symbolize_keys) { result }
should eq result
end
context 'and additional options are given' do
let(:options) { {id: :test} }
it 'passes them to initializer' do
expect(described_class).to receive(:new).
with(**input.symbolize_keys, **options) { result }
should eq result
end
end
end
context 'when input is an instance of described_class' do
let!(:input) { instance }
it 'returns input' do
expect(described_class).to_not receive(:new)
should eq input
end
end
context 'when input is a Symbol' do
let(:input) { :client_1 }
before { allow(Telegram).to receive(:bots) { {client_1: instance} } }
it { should eq Telegram.bots[:client_1] }
context 'and there is no such bot' do
let(:input) { :invalid }
it { expect { subject }.to raise_error(/not configured/) }
end
end
end
describe '.prepare_body' do describe '.prepare_body' do
subject { described_class.prepare_body(input) } subject { described_class.prepare_body(input) }
@@ -31,27 +97,49 @@ RSpec.describe Telegram::Bot::Client do
end end
describe '.new' do describe '.new' do
subject { described_class.new(*args) } subject { described_class.new(*args, **kwargs) }
let(:args) { [] }
let(:kwargs) { {} }
let(:token) { 'secret' }
let(:username) { 'superbot' }
context 'when multiple args are given' do context 'when multiple args are given' do
let(:args) { %w[secret superbot] } let(:args) { [token, username] }
its(:token) { should eq args[0] } its(:token) { should eq token }
its(:username) { should eq args[1] } its(:username) { should eq username }
its(:base_uri) { should include args[0] } its(:base_uri) { should eq "#{described_class::SERVER}/bot#{token}/" }
end end
context 'when hash is given' do context 'when hash is given' do
let(:args) { [token: 'secret', username: 'superbot'] } let(:kwargs) { {token: 'secret', username: 'superbot'} }
its(:token) { should eq args[0][:token] } its(:token) { should eq token }
its(:username) { should eq args[0][:username] } its(:username) { should eq username }
its(:base_uri) { should include args[0][:token] } its(:base_uri) { should eq "#{described_class::SERVER}/bot#{token}/" }
end
context 'with custom server' do
let(:server) { 'http://my.server' }
let(:args) { [token, username] }
let(:kwargs) { {server: server} }
its(:base_uri) { should eq "#{server}/bot#{token}/" }
context 'and hash options' do
let(:kwargs) { {token: token, username: username, server: server} }
its(:base_uri) { should eq "#{server}/bot#{token}/" }
end
end end
end end
describe '#request' do describe '#request' do
subject { -> { instance.request(action, request_body) } } subject { -> { instance.request(action, request_body) } }
let(:action) { :some_action } let(:action) { :some_action }
let(:url) { "#{format(described_class::URL_TEMPLATE, token: token)}#{action}" } let(:url) do
base_uri = format(described_class::URL_TEMPLATE,
server: described_class::SERVER,
token: token,
)
"#{base_uri}#{action}"
end
let(:request_body) { double(:body) } let(:request_body) { double(:body) }
let(:prepared_body) { double(:prepared_body) } let(:prepared_body) { double(:prepared_body) }
let(:response) { HTTP::Message.new_response(body).tap { |x| x.status = status } } let(:response) { HTTP::Message.new_response(body).tap { |x| x.status = status } }

Просмотреть файл

@@ -37,11 +37,13 @@ RSpec.describe Telegram::Bot::ClientStub do
end end
describe '#new' do describe '#new' do
subject { described_class.new(*args) } subject { described_class.new(*args, **kwargs) }
let(:args) { [] }
let(:kwargs) { {} }
context 'when only username is given' do context 'when only username is given' do
let(:args) { 'superbot' } let(:args) { ['superbot'] }
its(:username) { should eq args } its(:username) { should eq args[0] }
end end
context 'when username and token are given' do context 'when username and token are given' do
@@ -51,9 +53,9 @@ RSpec.describe Telegram::Bot::ClientStub do
end end
context 'when hash config is given' do context 'when hash config is given' do
let(:args) { [token: 'token', username: 'superbot'] } let(:kwargs) { {token: 'token', username: 'superbot'} }
its(:token) { should eq args[0][:token] } its(:token) { should eq kwargs[:token] }
its(:username) { should eq args[0][:username] } its(:username) { should eq kwargs[:username] }
end end
end end
end end

Просмотреть файл

@@ -41,57 +41,61 @@ RSpec.describe Telegram::Bot::RoutesHelper do
end end
describe '#telegram_webhook' do describe '#telegram_webhook' do
subject { mapper.telegram_webhook(*input) } subject { ->(*args, **kwargs) { mapper.telegram_webhook(*args, **kwargs) } }
let(:mapper) { double(:mapper).tap { |x| x.extend described_class } } let(:mapper) { double(:mapper).tap { |x| x.extend described_class } }
let(:bots) { {default: bot, other: other_bot} }
let(:controller) { double(:controller, name: :controller) } let(:controller) { double(:controller, name: :controller) }
let(:other_controller) { double(:other_controller, name: :other_controller) }
before { allow(Telegram).to receive(:bots) { bots } } before { allow(Telegram).to receive(:bots) { bots } }
def assert_routes(bot, controller, route_name, options) # rubocop:disable AbcSize def assert_route(bot, controller, path: nil, **expected_options) # rubocop:disable AbcSize
expected_path = options.delete(:path) || "telegram/#{bot.token}" path ||= "telegram/#{described_class.token_hash(bot.token)}"
expect(mapper).to receive(:post) do |path, params| expect(mapper).to receive(:post) do |actual_path, actual_options|
expect(path).to eq expected_path expect(actual_path).to eq(path)
middleware = params[:to] middleware = actual_options[:to]
expect(middleware.controller).to eq(controller) expect(middleware.controller).to eq(controller)
expect(middleware.bot.token).to eq(bot.token) expect(middleware.bot.token).to eq(bot.token)
expect(middleware.bot.username).to eq(bot.username) expect(middleware.bot.username).to eq(bot.username)
expect(params[:as]).to eq route_name expect(actual_options).to include(expected_options)
expect(params).to include(options) if options
end end
subject yield
end end
context 'when called with controller' do it 'creates routes for default bot' do
let(:input) { [controller, option: :val] } assert_route(bot, controller, as: 'default_telegram_webhook') do
subject[controller]
it 'creates routes for default bot and this controller' do
assert_routes bot, controller, 'default_telegram_webhook', option: :val
end end
context 'and bot does not have configured token' do other_controller = double(:other_controller, name: :other_controller)
let(:bot) { create_bot(nil) } assert_route(bot, other_controller, as: 'custom_route') do
it 'creates routes for default bot and this controller' do subject[other_controller, as: 'custom_route']
assert_routes bot, controller, 'default_telegram_webhook', option: :val
end
end end
end
context 'and bot has colon in token' do it 'passes extra options' do
let(:bot) { create_bot('some:token') } assert_route(bot, controller, as: 'default_telegram_webhook', option: :val, other: 2) do
it 'replaces colon with underscore' do subject[controller, option: :val, other: 2]
assert_routes bot, controller, 'default_telegram_webhook', end
option: :val, end
path: 'telegram/some_token'
it 'uses :path param to override default path' do
assert_route(bot, controller, as: 'default_telegram_webhook', path: 'custom/path') do
subject[controller, path: 'custom/path']
end
end
context 'when bot does not have configured token' do
let(:bot) { create_bot(nil) }
it 'creates route anyway' do
assert_route(bot, controller, as: 'default_telegram_webhook', path: 'telegram/') do
subject[controller]
end end
end end
end end
context 'when called with controller and smth castable to bot' do context 'when called with controller and smth castable to bot' do
let(:input) { [controller, 'custom_token', option: :val] }
it 'creates routes for every created bot and controller' do it 'creates routes for every created bot and controller' do
assert_routes create_bot('custom_token'), controller, 'telegram_webhook', assert_route(create_bot('custom_token'), controller, as: 'telegram_webhook') do
option: :val subject[controller, 'custom_token']
end
end end
end end
end end

Просмотреть файл

@@ -1,5 +1,26 @@
RSpec.describe Telegram::Bot::UpdatesController::Translation do RSpec.describe Telegram::Bot::UpdatesController::Translation do
def self.stub_translations(translations)
around do |ex|
backend = I18n.backend
I18n.backend = I18n::Backend::Simple.new
begin
I18n.backend.store_translations 'en', telegram: {webhooks: translations}
ex.run
ensure
I18n.backend = backend
end
end
end
let(:result) { double(:result) }
describe '#translate' do describe '#translate' do
stub_translations(
start: {
hello: 'Hello from start!',
hi: 'Hi %{name} from start', # rubocop:disable Style/FormatStringToken
},
)
let(:controller) do let(:controller) do
double( double(
controller_path: 'telegram/webhooks', controller_path: 'telegram/webhooks',
@@ -10,12 +31,28 @@ RSpec.describe Telegram::Bot::UpdatesController::Translation do
it 'uses action_name without ! for lazy translations' do it 'uses action_name without ! for lazy translations' do
expect(I18n).to receive(:translate).with('telegram.webhooks.start.hello', expect(I18n).to receive(:translate).with('telegram.webhooks.start.hello',
default: [:'telegram.webhooks.hello'], default: [:'telegram.webhooks.hello'],
) ) { result }
controller.t('.hello') expect(controller.t('.hello')).to eq(result)
expect(I18n).to receive(:translate).with('telegram.webhooks.start.hi',
default: [:'telegram.webhooks.hi'],
name: 'Dude',
) { result }
expect(controller.t('.hi', name: 'Dude')).to eq(result)
end
it 'translates' do
expect(controller.t('.hello')).to eq('Hello from start!')
expect(controller.t('.hi', name: 'Dude')).to eq('Hi Dude from start')
end end
end end
describe described_class::ClassMethods do describe described_class::ClassMethods do
stub_translations(
hello: 'Hello!',
hi: 'Hi %{name}', # rubocop:disable Style/FormatStringToken
)
describe '#translate' do describe '#translate' do
let(:controller_class) do let(:controller_class) do
double(controller_path: 'telegram/webhooks'). double(controller_path: 'telegram/webhooks').
@@ -23,8 +60,20 @@ RSpec.describe Telegram::Bot::UpdatesController::Translation do
end end
it 'uses controller_path for lazy translations' do it 'uses controller_path for lazy translations' do
expect(I18n).to receive(:translate).with('telegram.webhooks.hello', {}) expect(I18n).to receive(:translate) do |*args, **kwargs|
controller_class.t('.hello') # not using .with to support ruby 2.x and 3.x
expect([*args, kwargs]).to eq(['telegram.webhooks.hello', {}])
result
end
expect(controller_class.t('.hello')).to eq(result)
expect(I18n).to receive(:translate).with('telegram.webhooks.hi', name: 'Dude') { result }
expect(controller_class.t('.hi', name: 'Dude')).to eq(result)
end
it 'translates' do
expect(controller_class.t('.hello')).to eq('Hello!')
expect(controller_class.t('.hi', name: 'Dude')).to eq('Hi Dude')
end end
end end
end end

Просмотреть файл

@@ -9,14 +9,19 @@ RSpec.describe Telegram::Bot::UpdatesController::TypedUpdate do
context 'when `update` is a virtus model' do context 'when `update` is a virtus model' do
subject { controller } subject { controller }
unique_types = Telegram::Bot::UpdatesController::PAYLOAD_TYPES - %w[ unique_types = (Telegram::Bot::UpdatesController::PAYLOAD_TYPES - %w[
edited_message edited_message
channel_post channel_post
edited_channel_post edited_channel_post
] my_chat_member
unique_types.each do |type| chat_member
]).
map { |x| [x, Telegram::Bot::Types.const_get(type.camelize)] }.to_h.
merge(
'chat_member' => Telegram::Bot::Types::ChatMemberUpdated
)
unique_types.each do |type, type_class|
context "with #{type}" do context "with #{type}" do
type_class = Telegram::Bot::Types.const_get(type.camelize)
let(:payload_type) { type } let(:payload_type) { type }
let(:payload) do let(:payload) do
{}.tap do |result| {}.tap do |result|

Просмотреть файл

@@ -26,6 +26,12 @@ RSpec.describe Telegram::Bot::UpdatesController do
it { should eq [payload_type, payload.values_at(:data)] } it { should eq [payload_type, payload.values_at(:data)] }
end end
context 'when payload is poll_answer' do
let(:payload_type) { 'poll_answer' }
let(:payload) { stub_payload(:poll_id, :user, :option_ids) }
it { should eq [payload_type, payload.values_at(:poll_id, :option_ids)] }
end
context 'when payload is not supported' do context 'when payload is not supported' do
let(:payload_type) { '_unsupported_' } let(:payload_type) { '_unsupported_' }
it { should eq [:unsupported_payload_type, []] } it { should eq [:unsupported_payload_type, []] }
@@ -39,6 +45,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
inline_query inline_query
chosen_inline_result chosen_inline_result
callback_query callback_query
poll_answer
] ]
(described_class::PAYLOAD_TYPES - custom_payload_types).each do |type| (described_class::PAYLOAD_TYPES - custom_payload_types).each do |type|
context "when payload is #{type}" do context "when payload is #{type}" do

Просмотреть файл

@@ -17,15 +17,12 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib'] spec.require_paths = ['lib']
spec.post_install_message = 'Breaking changes in v0.14! ' \ spec.required_ruby_version = '>= 2.4'
'See upgrade guide at https://github.com/telegram-bot-rb/telegram-bot/wiki/Upgrading-to-0.14'
spec.required_ruby_version = '~> 2.0' spec.add_dependency 'actionpack', '>= 4.0', '< 6.2'
spec.add_dependency 'activesupport', '>= 4.0', '< 6.2'
spec.add_dependency 'actionpack', '>= 4.0', '< 6.0'
spec.add_dependency 'activesupport', '>= 4.0', '< 6.0'
spec.add_dependency 'httpclient', '~> 2.7' spec.add_dependency 'httpclient', '~> 2.7'
spec.add_development_dependency 'bundler', '~> 1.16' spec.add_development_dependency 'bundler', '> 1.16'
spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rake', '~> 13.0'
end end