1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-08-28 15:26:18 +03:00

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

..

45 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
cd2d3d8d87 v0.16.0 2023-11-09 21:33:24 -08:00
Max Melentiev
ee666f0e90 Merge pull request #207 from telegram-bot-rb/webhook_request
Add UpdatesController#webhook_request
2023-11-09 21:27:39 -08:00
Max Melentiev
759b125d82 Merge branch 'master' into webhook_request 2023-11-09 21:16:12 -08:00
Max Melentiev
21ca16f613 Merge pull request #209 from anko20094/added-chat-join-request-to-payload-types
Added chat_join_request to PAYLOAD_TYPES + update API
2023-11-09 21:14:23 -08:00
Max Melentiev
7cfceb7439 Update CHANGELOG.md 2023-11-09 21:01:07 -08:00
Max Melentiev
29603e5b60 Update .gitignore 2023-11-09 21:00:36 -08:00
Max Melentiev
5822eff988 Merge branch 'master' into added-chat-join-request-to-payload-types 2023-11-09 20:59:43 -08:00
Max Melentiev
cbe7186c44 Add UpdatesController#webhook_request 2023-11-09 20:56:45 -08:00
Max Melentiev
ca20bb0533 Merge pull request #221 from epergo/ep/fix-pipeline
Update testing matrix
2023-11-05 06:50:16 -08:00
Eloy Pérez
6f8565fbd5 Update testing matrix 2023-11-05 10:23:40 +01:00
Max Melentiev
a16928267a Merge pull request #217 from epergo/ep/rails-7-1
Allow installation in Rails 7.1
2023-10-28 07:59:03 +01:00
Eloy Pérez
83bc13bac6 Allow installation in Rails 7.1 2023-10-27 19:43:17 +02:00
Max Melentiev
1e1814a26d Merge pull request #213 from Nmerey/add-login-url-method
Add new commands available at API 6.8
2023-08-28 11:56:39 +01:00
Nmerey
ae9a6a501d Add new methods available at API 6.8 2023-08-28 14:51:15 +06:00
Danyil Shkoropad
74c2c28ab5 Added chat_join_request to PAYLOAD_TYPES + update API 2023-06-28 23:54:47 +03:00
Max Melentiev
af85966939 Fix status badge 2023-06-28 21:23:43 +01:00
Max Melentiev
5145c31d73 Merge pull request #210 from telegram-bot-rb/gh-actions
Move to github actions from travis-ci
2023-06-28 21:15:26 +01:00
Max Melentiev
cbb3a85757 Move to github actions from travis-ci 2023-06-28 20:55:51 +01:00
Max Melentiev
f0c5222c8f Merge pull request #206 from marckohlbrugge/master
Add commands upto Bot API 6.6
2023-04-16 19:18:51 +01:00
Marc Köhlbrugge
b4e5b056e0 Add commands upto Bot API 6.6 2023-04-06 23:11:31 +08:00
Max Melentiev
be74094c90 Merge pull request #200 from ceritium/patch-1
Fix typo on comment
2023-02-13 08:47:48 +01:00
Jose Galisteo
e53c31b79f Fix typo on comment 2023-02-11 14:56:39 +01:00
Max Melentiev
b40e4b432d Merge pull request #198 from telegram-bot-rb/nested_files
Add support for editMessageMedia similar to sendMediaGroup
2023-01-14 13:18:38 +00:00
Max Melentiev
ef5ec81386 Add support for editMessageMedia similar to sendMediaGroup 2023-01-13 20:33:30 +00:00
Max Melentiev
7d8a19d3c8 Pass action parameter to RequestBodyFormatter 2022-03-13 21:39:55 +00:00
Max Melentiev
380f6686fc Update to Bot API 5.7 2022-03-12 19:37:53 +00:00
Max Melentiev
ea64230eab Merge pull request #168 from telegram-bot-rb/nested_files
Fix sending File objects in nested objects (ex., in sendMediaGroup)
2022-03-12 19:34:57 +00:00
Max Melentiev
5b9706ed47 Fix sending File objects in nested objects in sendMediaGroup 2022-03-12 19:33:17 +00:00
Max Melentiev
2d73d1c879 Merge pull request #174 from telegram-bot-rb/rails-7
Rails 7.0 support
2022-01-04 22:54:17 +00:00
Max Melentiev
88af1152cf Rails 7.0 support 2022-01-04 22:49:53 +00:00
Max Melentiev
586217585a Merge pull request #164 from KonstantinReido/patch-1
Fix typo in README.md
2021-08-20 12:36:46 +01:00
Konstantin Reido
f83bf92601 Fix typo in README.md 2021-08-20 11:26:00 +03:00
Max Melentiev
3049ced903 Fix tests 2021-06-26 21:25:43 +01:00
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
40 изменённых файлов: 598 добавлений и 224 удалений

43
.github/workflows/ci.yml поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,43 @@
name: Lint & Test
on: [push, pull_request]
jobs:
# lint:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - uses: ruby/setup-ruby@v1
# with:
# ruby-version: '3.0'
# bundler-cache: true
# - run: bundle exec rubocop
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby:
- '2.7'
gemfile:
- gemfiles/rails_61.gemfile
- gemfiles/rails_60.gemfile
- gemfiles/rails_52.gemfile
- gemfiles/rails_51.gemfile
include:
# ruby 3.x
- ruby: '3.2'
gemfile: gemfiles/rails_71.gemfile
- ruby: '3.0'
gemfile: gemfiles/rails_61.gemfile
- ruby: '3.0'
gemfile: gemfiles/rails_60.gemfile
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bundle exec rake spec

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

@@ -1,25 +0,0 @@
language: ruby
cache: bundler
rvm:
- 2.6
- 2.5
gemfile:
- gemfiles/rails_61.gemfile
- gemfiles/rails_60.gemfile
- gemfiles/rails_52.gemfile
matrix:
include:
# old rails
- rvm: 2.5
gemfile: gemfiles/rails_51.gemfile
- rvm: 2.5
gemfile: gemfiles/rails_50.gemfile
- rvm: 2.5
gemfile: gemfiles/rails_42.gemfile
# old rubies
- rvm: 2.4
gemfile: gemfiles/rails_52.gemfile
- rvm: 2.3
gemfile: gemfiles/rails_52.gemfile
notifications:
email: false

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

@@ -1,3 +1,13 @@
appraise 'rails-71' do
gem 'actionpack', '~> 7.1.0'
gem 'railties', '~> 7.1.0'
end
appraise 'rails-70' do
gem 'actionpack', '~> 7.0.0'
gem 'railties', '~> 7.0.0'
end
appraise 'rails-61' do
gem 'actionpack', '~> 6.1.0'
gem 'railties', '~> 6.1.0'
@@ -17,13 +27,3 @@ appraise 'rails-51' do
gem 'actionpack', '~> 5.1.0'
gem 'railties', '~> 5.1.0'
end
appraise 'rails-50' do
gem 'actionpack', '~> 5.0.0'
gem 'railties', '~> 5.0.0'
end
appraise 'rails-42' do
gem 'actionpack', '~> 4.2.0'
gem 'railties', '~> 4.2.0'
end

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

@@ -1,5 +1,34 @@
# Unreleased
# 0.16.0
- Add `UpdatesController#webhook_request` that is set to `ActionDispatch::Request`
in webhook mode.
- Update to Bot API 6.8
- Added `chat_join_request` to PAYLOAD_TYPES
# 0.15.7
- Add support for editMessageMedia similar to sendMediaGroup.
# 0.15.6
- Rails 7.0 support.
- Fix sending File objects in nested objects in sendMediaGroup.
- Update to Bot API 5.7
# 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.

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

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

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

@@ -2,7 +2,7 @@
[![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)
[![Build Status](https://travis-ci.org/telegram-bot-rb/telegram-bot.svg)](https://travis-ci.org/telegram-bot-rb/telegram-bot)
[![Lint & Test](https://github.com/telegram-bot-rb/telegram-bot/actions/workflows/ci.yml/badge.svg)](https://github.com/telegram-bot-rb/telegram-bot/actions/workflows/ci.yml)
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).
@@ -25,9 +25,33 @@ with session, keyboards and inline queries.
Run it on your local machine in 1 minute!
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
@@ -39,11 +63,15 @@ gem 'telegram-bot'
And then execute:
$ bundle
```
bundle
```
Or install it yourself as:
$ gem install telegram-bot
```
gem install telegram-bot
```
Require if necessary:
@@ -131,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`.
See [`Client`](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/client.rb)
class for list of available methods. Please open PR or issue if it misses methods from
new API versions.
See [list of available methods](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/client/api_methods.txt)
fetched from [API docs](https://core.telegram.org/bots/api).
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.
@@ -170,19 +198,18 @@ Bot controllers like usual rails controllers provides features like callbacks,
```ruby
class Telegram::WebhookController < Telegram::Bot::UpdatesController
# use callbacks like in any other controllers
# use callbacks like in any other controller
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.
# 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)
# message can be also accessed via instance method
message == self.payload # true
# store_message(message['text'])
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)
# inline_query(query, offset)
@@ -193,14 +220,19 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
# 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!(data = nil, *)
# do_smth_with(data)
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` 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!'
# 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
@@ -223,10 +255,10 @@ end
#### Reply helpers
There are helpers to respond for basic actions. They just set chat/message/query
identifiers from update. See
There are helpers for basic responses. They just set chat/message/query
identifiers from the update. See
[`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
def respond_with(type, params); end
@@ -389,7 +421,7 @@ telegram_webhook TelegramAuctionController, :auction
telegram_webhook TelegramController, as: :custom_telegram_webhook
```
#### Processesing updates
#### Processing updates
To process update with controller call `.dispatch(bot, update)` on it.
There are several options to run it automatically:
@@ -398,6 +430,10 @@ There are several options to run it automatically:
- Use `Telegram::Bot::Middleware` with rack ([example in wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki/Not-rails-application)).
- 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:
@@ -507,7 +543,7 @@ 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 exaple,
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.

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

@@ -1,21 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 4.2.0"
gem "railties", "~> 4.2.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
end
gemspec path: "../"

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

@@ -1,21 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 5.0.0"
gem "railties", "~> 5.0.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
end
gemspec path: "../"

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

@@ -7,15 +7,15 @@ gem "railties", "~> 5.1.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "pry", "~> 0.14.1"
gem "pry-byebug", "~> 3.10.1"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.3"
gem "rspec", "~> 3.12.0"
gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
gem "coveralls", "~> 0.8.23", require: false
end
gemspec path: "../"

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

@@ -7,15 +7,15 @@ gem "railties", "~> 5.2.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "pry", "~> 0.14.1"
gem "pry-byebug", "~> 3.10.1"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.3"
gem "rspec", "~> 3.12.0"
gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
gem "coveralls", "~> 0.8.23", require: false
end
gemspec path: "../"

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

@@ -7,15 +7,15 @@ gem "railties", "~> 6.0.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "pry", "~> 0.14.1"
gem "pry-byebug", "~> 3.10.1"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.3"
gem "rspec", "~> 3.12.0"
gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
gem "coveralls", "~> 0.8.23", require: false
end
gemspec path: "../"

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

@@ -7,15 +7,15 @@ gem "railties", "~> 6.1.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1"
gem "pry-byebug", "~> 3.2.0"
gem "sdoc", "~> 0.4.1"
gem "telegram-bot-types", "~> 0.6.2"
gem "rspec", "~> 3.5.0"
gem "rspec-its", "~> 1.1.0"
gem "rspec-rails", "~> 3.5.0"
gem "pry", "~> 0.14.1"
gem "pry-byebug", "~> 3.10.1"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.3"
gem "rspec", "~> 3.12.0"
gem "rspec-its", "~> 1.3.0"
gem "rspec-rails", "~> 4.0.2"
gem "rubocop", "~> 0.52.1"
gem "coveralls", "~> 0.8.2", require: false
gem "coveralls", "~> 0.8.23", require: false
end
gemspec path: "../"

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

@@ -0,0 +1,21 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 7.0.0"
gem "railties", "~> 7.0.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.14.1"
gem "pry-byebug", "~> 3.10.1"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.3"
gem "rspec", "~> 3.12.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: "../"

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

@@ -0,0 +1,21 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "actionpack", "~> 7.1.0"
gem "railties", "~> 7.1.0"
group :development do
gem "appraisal", "~> 2.2"
gem "pry", "~> 0.14.1"
gem "pry-byebug", "~> 3.10.1"
gem "sdoc", "~> 2.0.3"
gem "telegram-bot-types", "~> 0.6.3"
gem "rspec", "~> 3.12.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: "../"

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

@@ -1,5 +1,4 @@
require 'active_support/core_ext/hash/keys'
require 'json'
require 'httpclient'
module Telegram
@@ -8,7 +7,9 @@ module Telegram
SERVER = 'https://api.telegram.org'.freeze
URL_TEMPLATE = '%<server>s/bot%<token>s/'.freeze
autoload :RequestBodyFormatter, 'telegram/bot/client/request_body_formatter'
autoload :TypedResponse, 'telegram/bot/client/typed_response'
prepend Async
include DebugClient
@@ -35,16 +36,8 @@ module Telegram
prepend TypedResponse
end
# Encodes nested hashes as json.
def prepare_body(body)
body = body.dup
body.each do |k, val|
body[k] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
end
end
def prepare_async_args(action, body = {})
[action.to_s, Async.prepare_hash(prepare_body(body))]
[action.to_s, Async.prepare_hash(RequestBodyFormatter.format(body, action))]
end
def error_for_response(response)
@@ -70,7 +63,7 @@ module Telegram
end
def request(action, body = {})
response = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
response = http_request("#{base_uri}#{action}", RequestBodyFormatter.format(body, action))
raise self.class.error_for_response(response) if response.status >= 300
JSON.parse(response.body)
end

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

@@ -1,5 +1,5 @@
# Generated with bin/fetch-telegram-methods
# Bot API 5.0
# Bot API 6.8
getUpdates
setWebhook
@@ -21,8 +21,6 @@ sendVoice
sendVideoNote
sendMediaGroup
sendLocation
editMessageLiveLocation
stopMessageLiveLocation
sendVenue
sendContact
sendPoll
@@ -30,13 +28,20 @@ sendDice
sendChatAction
getUserProfilePhotos
getFile
kickChatMember
banChatMember
unbanChatMember
restrictChatMember
promoteChatMember
setChatAdministratorCustomTitle
banChatSenderChat
unbanChatSenderChat
setChatPermissions
exportChatInviteLink
createChatInviteLink
editChatInviteLink
revokeChatInviteLink
approveChatJoinRequest
declineChatJoinRequest
setChatPhoto
deleteChatPhoto
setChatTitle
@@ -47,33 +52,68 @@ unpinAllChatMessages
leaveChat
getChat
getChatAdministrators
getChatMembersCount
getChatMemberCount
getChatMember
setChatStickerSet
deleteChatStickerSet
getForumTopicIconStickers
createForumTopic
editForumTopic
closeForumTopic
reopenForumTopic
deleteForumTopic
unpinAllForumTopicMessages
editGeneralForumTopic
closeGeneralForumTopic
reopenGeneralForumTopic
hideGeneralForumTopic
unhideGeneralForumTopic
unpinAllGeneralForumTopicMessages
answerCallbackQuery
setMyCommands
deleteMyCommands
getMyCommands
setMyName
getMyName
setMyDescription
getMyDescription
setMyShortDescription
getMyShortDescription
setChatMenuButton
getChatMenuButton
setMyDefaultAdministratorRights
getMyDefaultAdministratorRights
editMessageText
editMessageCaption
editMessageMedia
editMessageLiveLocation
stopMessageLiveLocation
editMessageReplyMarkup
stopPoll
deleteMessage
sendSticker
getStickerSet
getCustomEmojiStickers
uploadStickerFile
createNewStickerSet
addStickerToSet
setStickerPositionInSet
deleteStickerFromSet
setStickerSetThumb
setStickerEmojiList
setStickerKeywords
setStickerMaskPosition
setStickerSetTitle
setStickerSetThumbnail
setCustomEmojiStickerSetThumbnail
deleteStickerSet
answerInlineQuery
answerWebAppQuery
sendInvoice
createInvoiceLink
answerShippingQuery
answerPreCheckoutQuery

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

@@ -0,0 +1,61 @@
require 'json'
module Telegram
module Bot
class Client
# Encodes nested hashes and arrays as json and extract File objects from them
# to the top level. Top-level File objects are handled by httpclient.
# More details: https://core.telegram.org/bots/api#sending-files
module RequestBodyFormatter
extend self
def format(body, action)
body = body.dup
case action.to_s
when 'sendMediaGroup'
extract_files_from_array!(body, :media)
when 'editMessageMedia'
replace_field(body, :media) do |value|
files = {}
extract_files_from_hash(value, files).tap { body.merge!(files) }
end
end
body.each do |key, val|
body[key] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
end
end
private
# Detects field by symbol or string name and replaces it with mapped value.
def replace_field(hash, field_name)
field_name = [field_name.to_sym, field_name.to_s].find { |x| hash.key?(x) }
hash[field_name] = yield hash[field_name] if field_name
end
def extract_files_from_array!(hash, field_name)
replace_field(hash, field_name) do |value|
break value unless value.is_a?(Array)
files = {}
value.map { |x| extract_files_from_hash(x, files) }.
tap { hash.merge!(files) }
end
end
# Replace File objects with `attach` URIs. File objects are added into `files` hash.
def extract_files_from_hash(hash, files)
return hash unless hash.is_a?(Hash)
hash.transform_values do |value|
if value.is_a?(File)
arg_name = "_file#{files.size}"
files[arg_name] = value
"attach://#{arg_name}"
else
value
end
end
end
end
end
end
end

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

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

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

@@ -16,7 +16,7 @@ module Telegram
def call(env)
request = ActionDispatch::Request.new(env)
update = request.request_parameters
controller.dispatch(bot, update)
controller.dispatch(bot, update, request)
[200, {}, ['']]
end

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

@@ -99,6 +99,9 @@ module Telegram
pre_checkout_query
poll
poll_answer
my_chat_member
chat_member
chat_join_request
].freeze
class << self
@@ -115,21 +118,24 @@ module Telegram
end
end
attr_internal_reader :update, :bot, :payload, :payload_type
attr_internal_reader :bot, :payload, :payload_type, :update, :webhook_request
delegate :username, to: :bot, prefix: true, allow_nil: true
# Second argument can be either update object with hash access & string
# `update` can be either update object with hash access & string
# keys or Hash with `:from` or `:chat` to override this values and assume
# that update is nil.
def initialize(bot = nil, update = nil)
# ActionDispatch::Request object is passed in `webhook_request` when bot running
# in webhook mode.
def initialize(bot = nil, update = nil, webhook_request = nil)
if update.is_a?(Hash) && (update.key?(:from) || update.key?(:chat))
options = update
update = nil
end
@_update = update
@_bot = bot
@_update = update
@_chat, @_from = options && options.values_at(:chat, :from)
@_payload, @_payload_type = self.class.payload_from_update(update)
@_webhook_request = webhook_request
end
# Accessor to `'chat'` field of payload. Also tries `'chat'` in `'message'`

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

@@ -11,7 +11,7 @@ module Telegram
# respond_with :message, text: 'Where are you?'
# end
#
# def set_location_from_messge(city = nil, *)
# def set_location_from_message(city = nil, *)
# # update
# end
#

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

@@ -13,8 +13,9 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
x.extend Telegram::Bot::UpdatesController::Testing
end
end
let(:controller_args) { [bot, deep_stringify(update)] }
let(:controller_args) { [bot, deep_stringify(update), webhook_request] }
let(:update) { {payload_type => payload} }
let(:webhook_request) { nil }
let(:payload_type) { :some_type }
let(:payload) { double(:payload) }
let(:bot) { Telegram::Bot::ClientStub.new(bot_name) }
@@ -22,8 +23,8 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
let(:session) { controller.send(:session) }
# Process update.
def dispatch(update = self.update, bot = self.bot)
controller.dispatch_again(bot, deep_stringify(update))
def dispatch(update = self.update, bot = self.bot, webhook_request = self.webhook_request)
controller.dispatch_again(bot, deep_stringify(update), webhook_request)
end
# Same as `.as_json` but mocks-friendly.

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

@@ -5,9 +5,9 @@ module Telegram
IVARS_TO_KEEP = %i[@_session].freeze
# Perform multiple dispatches on same instance.
def dispatch_again(bot = nil, update = nil)
def dispatch_again(bot = nil, update = nil, webhook_request = nil)
recycle!
initialize(bot, update)
initialize(bot, update, webhook_request)
dispatch
end

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

@@ -12,15 +12,15 @@ module Telegram
module ClassMethods
# 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?('.')
I18n.translate(key, options)
I18n.translate(key, **options)
end
alias :t :translate
end
# See toplevel description.
def translate(key, options = {})
def translate(key, **options)
if key.to_s.start_with?('.')
path = controller_path.tr('/', '.')
defaults = [:"#{path}#{key}"]
@@ -28,7 +28,7 @@ module Telegram
options[:default] = defaults.flatten
key = "#{path}.#{action_name_i18n_key}#{key}"
end
I18n.translate(key, options)
I18n.translate(key, **options)
end
alias :t :translate

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

@@ -4,7 +4,7 @@ module Telegram
# Include this module to type cast update to Virtus model
# using `telegram-bot-types` gem (install this gem first).
module TypedUpdate
def initialize(bot = nil, update = nil)
def initialize(bot = nil, update = nil, webhook_request = nil)
update = Types::Update.new(update) if update && !update.is_a?(Types::Update)
super
end

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

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

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

@@ -10,4 +10,13 @@ RSpec.describe DefaultBotController, telegram_bot: :rails do
subject { -> { dispatch_command :load_session } }
it { should_not raise_error }
end
describe '#dispatch' do
it 'provides webhook_request' do
expect(described_class).to receive(:dispatch).
with(bot, hash_including('message'), instance_of(ActionDispatch::Request)).
and_call_original
dispatch_message :test
end
end
end

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

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

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

@@ -0,0 +1,92 @@
RSpec.describe Telegram::Bot::Client::RequestBodyFormatter do
describe '.format' do
subject { described_class.format(input, action) }
let(:action) { :sendMessage }
context 'when plain hash is given' do
let(:input) { {a: 1, b: '2', c: nil} }
it { should eq input }
end
context 'when nested hash is given' do
let(:input) { {a: 1, b: '2', c: [1, 2], d: {a: 1}, e: {b: []}} }
it 'encodes nested hashes to json' do
expected = input.dup
%i[c d e].each { |x| expected[x] = expected[x].to_json }
should eq expected
end
end
context 'with sendMediaGroup action' do
let(:action) { :sendMediaGroup }
let(:input) { {media: [{a: file_1}, {'b' => file_2, c: 123}, {d: 456}], x: 789} }
let(:file_1) { File.new(__FILE__) }
let(:file_2) { File.new(__FILE__) }
it 'extracts files to the top-level' do
should eq(
media: [
{a: 'attach://_file0'},
{b: 'attach://_file1', c: 123},
{d: 456},
].to_json,
x: 789,
'_file0' => file_1,
'_file1' => file_2,
)
end
context 'and input has string keys' do
let(:input) { super().stringify_keys }
it 'extracts files to the top-level' do
should eq(
'media' => [
{a: 'attach://_file0'},
{b: 'attach://_file1', c: 123},
{d: 456},
].to_json,
'x' => 789,
'_file0' => file_1,
'_file1' => file_2,
)
end
end
context 'without media' do
let(:input) { {a: 1, b: '2', c: nil} }
it { should eq input }
end
end
context 'with editMessageMedia action' do
let(:action) { :editMessageMedia }
let(:input) { {media: {a: file, b: 123}, x: 789} }
let(:file) { File.new(__FILE__) }
it 'extracts files to the top-level' do
should eq(
media: {a: 'attach://_file0', b: 123}.to_json,
x: 789,
'_file0' => file,
)
end
context 'and input has string keys' do
let(:input) { super().stringify_keys }
it 'extracts files to the top-level' do
should eq(
'media' => {a: 'attach://_file0', b: 123}.to_json,
'x' => 789,
'_file0' => file,
)
end
end
context 'without media' do
let(:input) { {a: 1, b: '2', c: nil} }
it { should eq input }
end
end
end
end

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

@@ -14,7 +14,10 @@ RSpec.describe Telegram::Bot::Client do
let(:input) { token }
it 'treats string as token' do
expect(described_class).to receive(:new).with(token, {}) { result }
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
@@ -68,25 +71,6 @@ RSpec.describe Telegram::Bot::Client do
end
end
describe '.prepare_body' do
subject { described_class.prepare_body(input) }
context 'when plain hash is given' do
let(:input) { {a: 1, b: '2', c: nil} }
it { should eq input }
end
context 'when nested hash is given' do
let(:input) { {a: 1, b: '2', c: [1, 2], d: {a: 1}, e: {b: []}} }
it 'encodes nested hashes to json' do
expected = input.dup
%i[c d e].each { |x| expected[x] = expected[x].to_json }
should eq expected
end
end
end
describe '.prepare_async_args' do
subject { described_class.prepare_async_args(*input) }
let(:input) { [:action, a: 1, b: :sym, c: [:other], 'd' => 'str'] }
@@ -94,7 +78,9 @@ RSpec.describe Telegram::Bot::Client do
end
describe '.new' do
subject { described_class.new(*args) }
subject { described_class.new(*args, **kwargs) }
let(:args) { [] }
let(:kwargs) { {} }
let(:token) { 'secret' }
let(:username) { 'superbot' }
@@ -106,7 +92,7 @@ RSpec.describe Telegram::Bot::Client do
end
context 'when hash is given' do
let(:args) { [token: 'secret', username: 'superbot'] }
let(:kwargs) { {token: 'secret', username: 'superbot'} }
its(:token) { should eq token }
its(:username) { should eq username }
its(:base_uri) { should eq "#{described_class::SERVER}/bot#{token}/" }
@@ -114,11 +100,12 @@ RSpec.describe Telegram::Bot::Client do
context 'with custom server' do
let(:server) { 'http://my.server' }
let(:args) { [token, username, server: server] }
let(:args) { [token, username] }
let(:kwargs) { {server: server} }
its(:base_uri) { should eq "#{server}/bot#{token}/" }
context 'and hash options' do
let(:args) { [token: token, username: username, server: server] }
let(:kwargs) { {token: token, username: username, server: server} }
its(:base_uri) { should eq "#{server}/bot#{token}/" }
end
end
@@ -142,7 +129,8 @@ RSpec.describe Telegram::Bot::Client do
let(:body) { body_json.to_json }
let(:body_json) { {'param' => 'val', 'description' => 'some description'} }
before do
expect(described_class).to receive(:prepare_body).with(request_body) { prepared_body }
expect(Telegram::Bot::Client::RequestBodyFormatter).
to receive(:format).with(request_body, action) { prepared_body }
expect(instance).to receive(:http_request).with(url, prepared_body) { response }
end
around { |ex| Telegram::Bot::ClientStub.stub_all!(false) { ex.run } }

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

@@ -37,11 +37,13 @@ RSpec.describe Telegram::Bot::ClientStub do
end
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
let(:args) { 'superbot' }
its(:username) { should eq args }
let(:args) { ['superbot'] }
its(:username) { should eq args[0] }
end
context 'when username and token are given' do
@@ -51,9 +53,9 @@ RSpec.describe Telegram::Bot::ClientStub do
end
context 'when hash config is given' do
let(:args) { [token: 'token', username: 'superbot'] }
its(:token) { should eq args[0][:token] }
its(:username) { should eq args[0][:username] }
let(:kwargs) { {token: 'token', username: 'superbot'} }
its(:token) { should eq kwargs[:token] }
its(:username) { should eq kwargs[:username] }
end
end
end

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

@@ -24,7 +24,8 @@ RSpec.describe Telegram::Bot::Middleware do
end
it 'calls dispatch on controller' do
expect(controller).to receive(:dispatch).with(bot, update)
expect(controller).to receive(:dispatch).
with(bot, update, instance_of(ActionDispatch::Request))
subject
end

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

@@ -41,7 +41,7 @@ RSpec.describe Telegram::Bot::RoutesHelper do
end
describe '#telegram_webhook' do
subject { ->(*args) { mapper.telegram_webhook(*args) } }
subject { ->(*args, **kwargs) { mapper.telegram_webhook(*args, **kwargs) } }
let(:mapper) { double(:mapper).tap { |x| x.extend described_class } }
let(:controller) { double(:controller, name: :controller) }
before { allow(Telegram).to receive(:bots) { bots } }

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

@@ -2,4 +2,13 @@ require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Poller integration spec', telegram_bot: :poller do
include_examples 'shared integration examples'
describe '#dispatch' do
it 'doesn`t provide webhook_request' do
expect(controller_class).to receive(:dispatch).
with(bot, hash_including('message')).
and_call_original
dispatch_message :test
end
end
end

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

@@ -17,4 +17,13 @@ RSpec.describe 'Rack integration spec', telegram_bot: :rack do
ActionDispatch::ParamsParser.new(app)
end
end
describe '#dispatch' do
it 'provides webhook_request' do
expect(controller_class).to receive(:dispatch).
with(bot, hash_including('message'), instance_of(ActionDispatch::Request)).
and_call_original
dispatch_message :test
end
end
end

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

@@ -1,3 +1,5 @@
require 'action_dispatch'
RSpec.describe Telegram::Bot::UpdatesController::Testing do
include_context 'telegram/bot/updates_controller'
let(:controller_class) do
@@ -24,4 +26,27 @@ RSpec.describe Telegram::Bot::UpdatesController::Testing do
it { should_not change { controller.send(:session)[:key] }.from 'sval' }
end
end
describe '#dispatch' do
it 'doesn`t provide webhook_request' do
dispatch
expect(controller.webhook_request).to eq nil
end
context 'when webhook_request is set' do
let(:webhook_request) { ActionDispatch::Request.new({}) }
it 'passes it to controller' do
dispatch
expect(controller.webhook_request).to eq webhook_request
end
end
context 'when webhook_request is given' do
it 'passes it to controller' do
webhook_request = ::ActionDispatch::Request.new({})
dispatch(update, bot, webhook_request)
expect(controller.webhook_request).to eq webhook_request
end
end
end
end

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

@@ -1,5 +1,26 @@
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
stub_translations(
start: {
hello: 'Hello from start!',
hi: 'Hi %{name} from start', # rubocop:disable Style/FormatStringToken
},
)
let(:controller) do
double(
controller_path: 'telegram/webhooks',
@@ -10,12 +31,28 @@ RSpec.describe Telegram::Bot::UpdatesController::Translation do
it 'uses action_name without ! for lazy translations' do
expect(I18n).to receive(:translate).with('telegram.webhooks.start.hello',
default: [:'telegram.webhooks.hello'],
)
controller.t('.hello')
) { result }
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
describe described_class::ClassMethods do
stub_translations(
hello: 'Hello!',
hi: 'Hi %{name}', # rubocop:disable Style/FormatStringToken
)
describe '#translate' do
let(:controller_class) do
double(controller_path: 'telegram/webhooks').
@@ -23,8 +60,20 @@ RSpec.describe Telegram::Bot::UpdatesController::Translation do
end
it 'uses controller_path for lazy translations' do
expect(I18n).to receive(:translate).with('telegram.webhooks.hello', {})
controller_class.t('.hello')
expect(I18n).to receive(:translate) do |*args, **kwargs|
# 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

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

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

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

@@ -46,6 +46,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
chosen_inline_result
callback_query
poll_answer
chat_join_request
]
(described_class::PAYLOAD_TYPES - custom_payload_types).each do |type|
context "when payload is #{type}" do

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

@@ -17,10 +17,10 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
spec.required_ruby_version = '~> 2.0'
spec.required_ruby_version = '>= 2.4'
spec.add_dependency 'actionpack', '>= 4.0', '< 6.2'
spec.add_dependency 'activesupport', '>= 4.0', '< 6.2'
spec.add_dependency 'actionpack', '>= 4.0', '< 7.2'
spec.add_dependency 'activesupport', '>= 4.0', '< 7.2'
spec.add_dependency 'httpclient', '~> 2.7'
spec.add_development_dependency 'bundler', '> 1.16'