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

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

..

59 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
533d84c9ce v0.16.3 2024-07-21 19:50:03 -04:00
Danyil Shkoropad
7ddebcb1e2 Add pre_checkout_query to PAYLOAD_TYPES (#238)
* Update Bot API version from 7.5 to 7.7
- Add new method: sendPaidMedia

* Add pre_checkout_query to PAYLOAD_TYPES

- Added pre_checkout_query to updates_controller.rb
- Updated tests in updates_controller_spec.rb
- Updated tests in typed_update_spec.rb
- Updated CHANGELOG.md for Bot API 7.7 and pre_checkout_query
2024-07-16 22:20:52 +01:00
Danyil Shkoropad
e7b7e2a219 Update Bot API version from 7.5 to 7.7 (#237)
- Add new method: sendPaidMedia
2024-07-08 21:05:45 +01:00
Danyil Shkoropad
7a34c0aa88 Bot API updated from 7.3 to 7.5 (#236) 2024-06-29 14:31:34 +01:00
Max Melentiev
62c3e3c861 v0.16.2 2024-05-12 16:55:41 -04:00
Max Melentiev
da22ecbe79 Update to Bot API 7.3 2024-05-12 16:54:41 -04:00
Max Melentiev
ffd17ec933 Fix frozen string issue for ruby < 3 (#235) 2024-05-12 21:42:33 +01:00
Max Melentiev
ad1eb5a47f Use debug gem instead of pry (#234) 2024-05-12 21:41:03 +01:00
Roman Novoselov
b05f17b02b Treat callback_query_id as string in spec helper (#231) 2024-04-16 18:15:15 +01:00
Max Melentiev
6140f7a68a Bot api 7.0 (#229) 2023-12-30 16:23:57 +00:00
Max Melentiev
57b17f068c Update rubopcop (#227)
* Update rubopcop [WIP]

* Further rubocop fixes

* Fix rubocop-rails cops

* Fix spec

* Disable rubocop suggestions

* Run rubocop in github actions rather than in codeclimate
2023-12-30 15:20:27 +00:00
Max Melentiev
7bcb0caded v0.16.1 2023-12-30 18:18:35 +06:00
Max Melentiev
d048f42c6c Merge pull request #225 from mario-amazing/master
Fix rails 7.1 broadcast error
2023-12-18 20:58:13 +00:00
mario_amazing
f54b36d9b7 Fix rails 7.1 broadcast error 2023-12-18 18:29:04 +03:00
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
93 изменённых файлов: 981 добавлений и 367 удалений

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

@@ -2,7 +2,3 @@ checks:
method-complexity: method-complexity:
config: config:
threshold: 6 # should be just fine threshold: 6 # should be just fine
plugins:
rubocop:
enabled: true

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.2'
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,15 +1,18 @@
# Rubocop does not support 2.0 require: rubocop-rails
Gemspec/RequiredRubyVersion: {Enabled: false}
Rails: {Enabled: true} AllCops:
TargetRubyVersion: 2.4
NewCops: enable
SuggestExtensions: false
Exclude:
- gemfiles/*
- tmp/*
- vendor/**/* # for github actions
Layout/AlignParameters: Gemspec/DevelopmentDependencies: {Enabled: false}
# Disable, till rubocop supports combination of styles.
# Use one of this styles where appropriate, keep it clean, compact and readable. Layout/ArgumentAlignment: {EnforcedStyle: with_fixed_indentation}
Enabled: false Layout/ParameterAlignment: {EnforcedStyle: with_fixed_indentation}
# EnforcedStyle:
# - with_first_parameter
# - with_fixed_indentation
# Breaks # Breaks
# #
@@ -19,7 +22,11 @@ Layout/AlignParameters:
# ) # )
Layout/ClosingParenthesisIndentation: {Enabled: false} Layout/ClosingParenthesisIndentation: {Enabled: false}
Layout/DotPosition: {EnforcedStyle: trailing} Layout/DotPosition: {EnforcedStyle: trailing}
Layout/EmptyLineAfterGuardClause: {Enabled: false}
Layout/FirstParameterIndentation: {EnforcedStyle: consistent} Layout/FirstParameterIndentation: {EnforcedStyle: consistent}
Layout/LineContinuationLeadingSpace: {Enabled: false}
Layout/LineEndStringConcatenationIndentation: {EnforcedStyle: indented}
Layout/LineLength: {Max: 100}
# Same as Layout/ClosingParenthesisIndentation # Same as Layout/ClosingParenthesisIndentation
Layout/MultilineMethodCallBraceLayout: {Enabled: false} Layout/MultilineMethodCallBraceLayout: {Enabled: false}
Layout/MultilineMethodCallIndentation: {EnforcedStyle: indented} Layout/MultilineMethodCallIndentation: {EnforcedStyle: indented}
@@ -28,8 +35,16 @@ Layout/SpaceInsideHashLiteralBraces: {EnforcedStyle: no_space}
# Offences named scopes and `expect {}.to change {}`. # Offences named scopes and `expect {}.to change {}`.
Lint/AmbiguousBlockAssociation: {Enabled: false} Lint/AmbiguousBlockAssociation: {Enabled: false}
Lint/EmptyBlock:
Exclude:
- spec/**/*
Naming/FileName:
Exclude:
- lib/tasks/telegram-bot.rake
Naming/MethodParameterName: {MinNameLength: 2}
Naming/PredicateName: {Enabled: false} Naming/PredicateName: {Enabled: false}
Naming/RescuedExceptionsVariableName: {PreferredName: exception}
Naming/VariableNumber: {EnforcedStyle: snake_case} Naming/VariableNumber: {EnforcedStyle: snake_case}
Style/Alias: {Enabled: false} Style/Alias: {Enabled: false}
@@ -45,12 +60,16 @@ Style/ModuleFunction: {Enabled: false}
Style/NestedParenthesizedCalls: {Enabled: false} Style/NestedParenthesizedCalls: {Enabled: false}
Style/SignalException: {EnforcedStyle: only_raise} Style/SignalException: {EnforcedStyle: only_raise}
Style/TrailingCommaInArguments: {Enabled: false} Style/TrailingCommaInArguments: {Enabled: false}
Style/TrailingCommaInLiteral: {EnforcedStyleForMultiline: comma} Style/TrailingCommaInArrayLiteral: {EnforcedStyleForMultiline: comma}
Style/TrailingCommaInHashLiteral: {EnforcedStyleForMultiline: comma}
Metrics/AbcSize: {Max: 21} Metrics/AbcSize: {Max: 21}
# Other metrics are just enough. # Other metrics are just enough.
# This one offences all specs, routes and some initializers. # This one offences all specs, routes and some initializers.
Metrics/BlockLength: {Enabled: false} Metrics/BlockLength: {Enabled: false}
Metrics/LineLength: {Max: 100}
Metrics/MethodLength: {Max: 30} Metrics/MethodLength: {Max: 30}
Metrics/CyclomaticComplexity: {Max: 8} Metrics/CyclomaticComplexity: {Max: 8}
Rails/IndexWith: {Enabled: false}
Rails/RakeEnvironment: {Enabled: false}
Rails/ShortI18n: {Enabled: false}

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

@@ -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,15 @@
# frozen_string_literal: true
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 appraise 'rails-61' do
gem 'actionpack', '~> 6.1.0' gem 'actionpack', '~> 6.1.0'
gem 'railties', '~> 6.1.0' gem 'railties', '~> 6.1.0'
@@ -17,13 +29,3 @@ appraise 'rails-51' do
gem 'actionpack', '~> 5.1.0' gem 'actionpack', '~> 5.1.0'
gem 'railties', '~> 5.1.0' gem 'railties', '~> 5.1.0'
end 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,48 @@
# Unreleased # Unreleased
# 0.16.3
- Update to Bot API 7.7
- Added `pre_checkout_query` to PAYLOAD_TYPES
# 0.16.2
- Update to Bot API 7.3
- Add `# frozen_string_literal: true` to all source files.
# 0.16.1
- Fix logger in poller mode in rails 7.1
# 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 # 0.15.1
- Rails 6.1 support. - Rails 6.1 support.

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

@@ -1,20 +1,22 @@
# frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
gemspec gemspec
group :development do group :development do
gem 'appraisal', '~> 2.2' gem 'appraisal', '~> 2.2'
gem 'pry', '~> 0.10.1' gem 'debug', '~> 1.9.2'
gem 'pry-byebug', '~> 3.2.0' gem 'sdoc', '~> 2.0.3'
gem 'sdoc', '~> 0.4.1'
gem 'telegram-bot-types', '~> 0.6.2' gem 'telegram-bot-types', '~> 0.6.3'
gem 'rspec', '~> 3.5.0' gem 'rspec', '~> 3.12.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', '~> 1.59.0'
gem 'rubocop-rails', '~> 2.23.1'
gem 'coveralls', '~> 0.8.2', require: false gem 'coveralls', '~> 0.8.23', require: false
end end

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

@@ -2,7 +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) [![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 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).
@@ -25,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
@@ -39,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:
@@ -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`. 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.
@@ -170,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)
@@ -193,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
@@ -223,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
@@ -389,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:
@@ -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 `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). - 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), To run action without update (ex., send notifications from jobs),
you can call `#process` directly. In this case controller can be initialized you can call `#process` directly. In this case controller can be initialized
with `:from` and/or `:chat` options instead of `update` object: 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. 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 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 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 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. the default way to test the bot.

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'bundler/gem_tasks' require 'bundler/gem_tasks'
require 'rspec/core/rake_task' require 'rspec/core/rake_task'

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

@@ -1,7 +1,11 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/setup' require 'bundler/setup'
require 'telegram/bot' require 'telegram/bot'
require 'pry' require 'irb'
Pry.start require 'irb/completion'
IRB.setup(nil)
IRB::Irb.new.run(IRB.conf)

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

@@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
# #
# Fetch list of methods from Telegram docs. # Fetch list of methods from Telegram docs.
# Use it to update client.rb. # Use it to update client.rb.
@@ -6,7 +8,7 @@
require 'net/http' require 'net/http'
require 'nokogiri' require 'nokogiri'
DOCS_URL = 'https://core.telegram.org/bots/api'.freeze DOCS_URL = 'https://core.telegram.org/bots/api'
page_html = Net::HTTP.get(URI(DOCS_URL)) page_html = Net::HTTP.get(URI(DOCS_URL))
doc = Nokogiri::HTML(page_html) doc = Nokogiri::HTML(page_html)
@@ -18,11 +20,11 @@ headers = doc.css('h3, h4').
map { |g| g.map(&:text) } map { |g| g.map(&:text) }
# Method starts with lowercase and does not have spaces. # Method starts with lowercase and does not have spaces.
NOT_METHOD_REGEXP = /(\A[^a-z])|\s/ NOT_METHOD_REGEXP = /(\A[^a-z])|\s/.freeze
# Filter method names. # Filter method names.
method_list = headers. method_list = headers.
map { |g| g.reject { |x| x.match?(NOT_METHOD_REGEXP) } }. map { |g| g.grep_v(NOT_METHOD_REGEXP) }.
reject(&:empty?) reject(&:empty?)
api_version = doc.text.match(/^(?:Introducing )?(Bot API ([\d\.]+))\.?$/) api_version = doc.text.match(/^(?:Introducing )?(Bot API ([\d\.]+))\.?$/)

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

@@ -1,6 +1,7 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
root = File.expand_path('../../', __FILE__) root = File.expand_path('..', __dir__)
hooks_dir = "#{root}/bin/git-hooks" hooks_dir = "#{root}/bin/git-hooks"
`ls -1 #{hooks_dir}`.each_line.map(&:strip).each do |file| `ls -1 #{hooks_dir}`.each_line.map(&:strip).each do |file|

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

@@ -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 group :development do
gem "appraisal", "~> 2.2" gem "appraisal", "~> 2.2"
gem "pry", "~> 0.10.1" gem "debug", "~> 1.9.2"
gem "pry-byebug", "~> 3.2.0" gem "sdoc", "~> 2.0.3"
gem "sdoc", "~> 0.4.1" gem "telegram-bot-types", "~> 0.6.3"
gem "telegram-bot-types", "~> 0.6.2" gem "rspec", "~> 3.12.0"
gem "rspec", "~> 3.5.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-rails", "~> 4.0.2"
gem "rspec-rails", "~> 3.5.0" gem "rubocop", "~> 1.59.0"
gem "rubocop", "~> 0.52.1" gem "rubocop-rails", "~> 2.23.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

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

@@ -7,15 +7,15 @@ 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 "debug", "~> 1.9.2"
gem "pry-byebug", "~> 3.2.0" gem "sdoc", "~> 2.0.3"
gem "sdoc", "~> 0.4.1" gem "telegram-bot-types", "~> 0.6.3"
gem "telegram-bot-types", "~> 0.6.2" gem "rspec", "~> 3.12.0"
gem "rspec", "~> 3.5.0" gem "rspec-its", "~> 1.3.0"
gem "rspec-its", "~> 1.1.0" gem "rspec-rails", "~> 4.0.2"
gem "rspec-rails", "~> 3.5.0" gem "rubocop", "~> 1.59.0"
gem "rubocop", "~> 0.52.1" gem "rubocop-rails", "~> 2.23.1"
gem "coveralls", "~> 0.8.2", require: false gem "coveralls", "~> 0.8.23", require: false
end end
gemspec path: "../" gemspec path: "../"

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

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

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

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

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
namespace :telegram do namespace :telegram do
namespace :bot do namespace :bot do
desc 'Run poller. It broadcasts Rails.logger to STDOUT in dev like `rails s` do. ' \ desc 'Run poller. It broadcasts Rails.logger to STDOUT in dev like `rails s` do. ' \
@@ -6,10 +8,14 @@ namespace :telegram do
ENV['BOT_POLLER_MODE'] = 'true' ENV['BOT_POLLER_MODE'] = 'true'
Rake::Task['environment'].invoke Rake::Task['environment'].invoke
if ENV.fetch('LOG_TO_STDOUT') { Rails.env.development? }.present? if ENV.fetch('LOG_TO_STDOUT') { Rails.env.development? }.present?
console = ActiveSupport::Logger.new(STDERR) console = ActiveSupport::Logger.new($stderr)
Rails.logger.extend ActiveSupport::Logger.broadcast console if Rails.logger.respond_to?(:broadcast_to)
Rails.logger.broadcast_to(console)
else
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
end end
Telegram::Bot::UpdatesPoller.start(ENV['BOT'].try!(:to_sym) || :default) Telegram::Bot::UpdatesPoller.start(ENV['BOT']&.to_sym || :default)
end end
desc 'Set webhook urls for all bots' desc 'Set webhook urls for all bots'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/config_methods' require 'telegram/bot/config_methods'
module Telegram module Telegram
@@ -12,15 +14,6 @@ module Telegram
# Raised for valid telegram response with 404 status code. # Raised for valid telegram response with 404 status code.
class NotFound < Error; end class NotFound < Error; end
module_function
def deprecation_0_16
@deprecation ||= begin
require 'active_support/deprecation'
ActiveSupport::Deprecation.new('0.16', 'Telegram::Bot')
end
end
autoload :Async, 'telegram/bot/async' autoload :Async, 'telegram/bot/async'
autoload :Client, 'telegram/bot/client' autoload :Client, 'telegram/bot/client'
autoload :ClientStub, 'telegram/bot/client_stub' autoload :ClientStub, 'telegram/bot/client_stub'
@@ -30,6 +23,7 @@ module Telegram
autoload :Tasks, 'telegram/bot/tasks' 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'
autoload :VERSION, 'telegram/bot/version'
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
# Telegram clients can perform requests in async way with # Telegram clients can perform requests in async way with
@@ -128,7 +130,7 @@ module Telegram
# #
# If no block is given returns previously set value or the global one, # If no block is given returns previously set value or the global one,
# set by #async=. # set by #async=.
def async(val = true) def async(val = true) # rubocop:disable Style/OptionalBooleanParameter
thread_key = object_id thread_key = object_id
thread_store = Async.thread_store thread_store = Async.thread_store
return thread_store.fetch(thread_key) { @async } unless block_given? return thread_store.fetch(thread_key) { @async } unless block_given?
@@ -137,7 +139,7 @@ module Telegram
thread_store[thread_key] = self.class.prepare_async_val(val) thread_store[thread_key] = self.class.prepare_async_val(val)
yield yield
ensure ensure
if MISSING_VALUE == old_val if old_val == MISSING_VALUE
thread_store.delete(thread_key) thread_store.delete(thread_key)
else else
thread_store[thread_key] = old_val thread_store[thread_key] = old_val

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

@@ -1,14 +1,17 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/keys'
require 'json'
require 'httpclient' require 'httpclient'
module Telegram module Telegram
module Bot module Bot
class Client class Client
SERVER = 'https://api.telegram.org'.freeze SERVER = 'https://api.telegram.org'
URL_TEMPLATE = '%<server>s/bot%<token>s/'.freeze URL_TEMPLATE = '%<server>s/bot%<token>s/'
autoload :RequestBodyFormatter, 'telegram/bot/client/request_body_formatter'
autoload :TypedResponse, 'telegram/bot/client/typed_response' autoload :TypedResponse, 'telegram/bot/client/typed_response'
prepend Async prepend Async
include DebugClient include DebugClient
@@ -35,20 +38,12 @@ module Telegram
prepend TypedResponse prepend TypedResponse
end 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 = {}) 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 end
def error_for_response(response) def error_for_response(response)
result = JSON.parse(response.body) rescue nil # rubocop:disable RescueModifier result = JSON.parse(response.body) rescue nil # rubocop:disable Style/RescueModifier
return Error.new(response.reason) unless result return Error.new(response.reason) unless result
message = result['description'] || '-' message = result['description'] || '-'
# This errors are raised only for valid responses from Telegram # This errors are raised only for valid responses from Telegram
@@ -70,7 +65,7 @@ module Telegram
end end
def request(action, body = {}) 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 raise self.class.error_for_response(response) if response.status >= 300
JSON.parse(response.body) JSON.parse(response.body)
end end

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

@@ -1,10 +1,12 @@
# frozen_string_literal: true
require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/inflections'
module Telegram module Telegram
module Bot module Bot
class Client class Client
module ApiHelper module ApiHelper
METHODS_LIST_FILE = File.expand_path('../api_methods.txt', __FILE__) METHODS_LIST_FILE = File.expand_path('api_methods.txt', __dir__)
class << self class << self
def methods_list(file = METHODS_LIST_FILE) def methods_list(file = METHODS_LIST_FILE)

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

@@ -1,5 +1,5 @@
# Generated with bin/fetch-telegram-methods # Generated with bin/fetch-telegram-methods
# Bot API 5.0 # Bot API 7.7
getUpdates getUpdates
setWebhook setWebhook
@@ -11,7 +11,9 @@ logOut
close close
sendMessage sendMessage
forwardMessage forwardMessage
forwardMessages
copyMessage copyMessage
copyMessages
sendPhoto sendPhoto
sendAudio sendAudio
sendDocument sendDocument
@@ -19,24 +21,31 @@ sendVideo
sendAnimation sendAnimation
sendVoice sendVoice
sendVideoNote sendVideoNote
sendPaidMedia
sendMediaGroup sendMediaGroup
sendLocation sendLocation
editMessageLiveLocation
stopMessageLiveLocation
sendVenue sendVenue
sendContact sendContact
sendPoll sendPoll
sendDice sendDice
sendChatAction sendChatAction
setMessageReaction
getUserProfilePhotos getUserProfilePhotos
getFile getFile
kickChatMember banChatMember
unbanChatMember unbanChatMember
restrictChatMember restrictChatMember
promoteChatMember promoteChatMember
setChatAdministratorCustomTitle setChatAdministratorCustomTitle
banChatSenderChat
unbanChatSenderChat
setChatPermissions setChatPermissions
exportChatInviteLink exportChatInviteLink
createChatInviteLink
editChatInviteLink
revokeChatInviteLink
approveChatJoinRequest
declineChatJoinRequest
setChatPhoto setChatPhoto
deleteChatPhoto deleteChatPhoto
setChatTitle setChatTitle
@@ -47,35 +56,76 @@ unpinAllChatMessages
leaveChat leaveChat
getChat getChat
getChatAdministrators getChatAdministrators
getChatMembersCount getChatMemberCount
getChatMember getChatMember
setChatStickerSet setChatStickerSet
deleteChatStickerSet deleteChatStickerSet
getForumTopicIconStickers
createForumTopic
editForumTopic
closeForumTopic
reopenForumTopic
deleteForumTopic
unpinAllForumTopicMessages
editGeneralForumTopic
closeGeneralForumTopic
reopenGeneralForumTopic
hideGeneralForumTopic
unhideGeneralForumTopic
unpinAllGeneralForumTopicMessages
answerCallbackQuery answerCallbackQuery
getUserChatBoosts
getBusinessConnection
setMyCommands setMyCommands
deleteMyCommands
getMyCommands getMyCommands
setMyName
getMyName
setMyDescription
getMyDescription
setMyShortDescription
getMyShortDescription
setChatMenuButton
getChatMenuButton
setMyDefaultAdministratorRights
getMyDefaultAdministratorRights
editMessageText editMessageText
editMessageCaption editMessageCaption
editMessageMedia editMessageMedia
editMessageLiveLocation
stopMessageLiveLocation
editMessageReplyMarkup editMessageReplyMarkup
stopPoll stopPoll
deleteMessage deleteMessage
deleteMessages
sendSticker sendSticker
getStickerSet getStickerSet
getCustomEmojiStickers
uploadStickerFile uploadStickerFile
createNewStickerSet createNewStickerSet
addStickerToSet addStickerToSet
setStickerPositionInSet setStickerPositionInSet
deleteStickerFromSet deleteStickerFromSet
setStickerSetThumb replaceStickerInSet
setStickerEmojiList
setStickerKeywords
setStickerMaskPosition
setStickerSetTitle
setStickerSetThumbnail
setCustomEmojiStickerSetThumbnail
deleteStickerSet
answerInlineQuery answerInlineQuery
answerWebAppQuery
sendInvoice sendInvoice
createInvoiceLink
answerShippingQuery answerShippingQuery
answerPreCheckoutQuery answerPreCheckoutQuery
getStarTransactions
refundStarPayment
setPassportDataErrors setPassportDataErrors

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

@@ -0,0 +1,63 @@
# frozen_string_literal: true
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

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/client/api_helper' require 'telegram/bot/client/api_helper'
require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/inflections'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
# Stubbed client for tests. Saves all requests into #requests hash. # Stubbed client for tests. Saves all requests into #requests hash.
@@ -5,11 +7,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
@@ -17,7 +19,7 @@ module Telegram
class << self class << self
# Any call to Client.new will return ClientStub instance when `enabled` is true. # Any call to Client.new will return ClientStub instance when `enabled` is true.
# Can be used with a block. # Can be used with a block.
def stub_all!(enabled = true) def stub_all!(enabled = true) # rubocop:disable Style/OptionalBooleanParameter
Client.extend(StubbedConstructor) unless Client < StubbedConstructor Client.extend(StubbedConstructor) unless Client < StubbedConstructor
return @_stub_all = enabled unless block_given? return @_stub_all = enabled unless block_given?
begin begin
@@ -34,7 +36,7 @@ module Telegram
end end
end end
def initialize(token = nil, username = nil, **options) def initialize(token = nil, username = nil, **options) # rubocop:disable Lint/MissingSuper
@token = token || options[:token] @token = token || options[:token]
@username = username || options[:username] || token @username = username || options[:username] || token
reset reset

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/indifferent_access'
@@ -44,7 +46,7 @@ module Telegram
# from `secrets.yml` merging `telegram['bot']` at `:default` key. # from `secrets.yml` merging `telegram['bot']` at `:default` key.
# #
# Can be overwritten with .bots_config= # Can be overwritten with .bots_config=
def bots_config def bots_config # rubocop:disable Metrics/PerceivedComplexity
@bots_config ||= @bots_config ||=
if defined?(Rails.application) if defined?(Rails.application)
app = Rails.application app = Rails.application

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

@@ -1,7 +1,9 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
module DebugClient module DebugClient
def debug!(dev = STDOUT) def debug!(dev = $stdout)
if block_given? if block_given?
begin begin
old_dev = client.debug_dev old_dev = client.debug_dev

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'active_support/concern' require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/json' require 'active_support/json'
@@ -16,12 +18,12 @@ module Telegram
def call(env) def call(env)
request = ActionDispatch::Request.new(env) request = ActionDispatch::Request.new(env)
update = request.request_parameters update = request.request_parameters
controller.dispatch(bot, update) controller.dispatch(bot, update, request)
[200, {}, ['']] [200, {}, ['']]
end end
def inspect def inspect
"#<#{self.class.name}(#{controller.try!(:name)})>" "#<#{self.class.name}(#{controller&.name})>"
end end
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/routes_helper' require 'telegram/bot/routes_helper'
module Telegram module Telegram
@@ -10,7 +12,7 @@ module Telegram
end end
config.before_initialize do config.before_initialize do
::ActionDispatch::Routing::Mapper.send(:include, RoutesHelper) ::ActionDispatch::Routing::Mapper.include RoutesHelper
end end
initializer 'telegram.bot.updates_controller.set_config' do |app| initializer 'telegram.bot.updates_controller.set_config' do |app|

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'base64' require 'base64'
require 'openssl' require 'openssl'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
module RSpec module RSpec

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec' require 'telegram/bot/rspec'
require 'telegram/bot/rspec/message_helpers' require 'telegram/bot/rspec/message_helpers'
@@ -7,7 +9,7 @@ RSpec.shared_context 'telegram/bot/callback_query' do
subject { -> { dispatch callback_query: payload } } subject { -> { dispatch callback_query: payload } }
let(:payload) { {id: callback_query_id, from: from, message: message, data: data} } let(:payload) { {id: callback_query_id, from: from, message: message, data: data} }
let(:callback_query_id) { 11 } let(:callback_query_id) { '11' }
let(:message_id) { 22 } let(:message_id) { 22 }
let(:message) { {message_id: message_id, chat: chat, text: 'message text'} } let(:message) { {message_id: message_id, chat: chat, text: 'message text'} }
let(:data) { raise '`let(:data) { "callback query data here" }` is required' } let(:data) { raise '`let(:data) { "callback query data here" }` is required' }

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
module RSpec module RSpec
@@ -61,14 +63,14 @@ module Telegram
attr_reader :performed_requests, :description attr_reader :performed_requests, :description
def initialize(bot, action, description: nil) def initialize(bot, action, description: nil) # rubocop:disable Lint/MissingSuper
@bot = bot @bot = bot
@action = action @action = action
@description = description || "make #{action} telegram request" @description = description || "make #{action} telegram request"
exactly(1) exactly(1)
end end
def matches?(proc) # rubocop:disable AbcSize def matches?(proc) # rubocop:disable Metrics/AbcSize
raise ArgumentError, 'matcher only supports block expectations' unless proc.is_a?(Proc) raise ArgumentError, 'matcher only supports block expectations' unless proc.is_a?(Proc)
original_requests_count = bot.requests[action].count original_requests_count = bot.requests[action].count
proc.call proc.call
@@ -115,15 +117,15 @@ module Telegram
:arg_list_matcher, :matching_requests_count :arg_list_matcher, :matching_requests_count
def base_message def base_message
"make #{expectation_type.to_s.tr('_', ' ')} #{expected_number} " \ msg = "make #{expectation_type.to_s.tr('_', ' ')} #{expected_number} " \
"#{bot.inspect}.#{action} requests,".tap do |msg| "#{bot.inspect}.#{action} requests,"
msg << " with #{arg_list_matcher}," if arg_list_matcher msg += " with #{arg_list_matcher}," if arg_list_matcher
msg << " but made #{matching_requests_count}" msg += " but made #{matching_requests_count}"
if performed_requests if performed_requests
actual_args = performed_requests.map(&:inspect).join(', ') actual_args = performed_requests.map(&:inspect).join(', ')
msg << ", and #{performed_requests.count} with #{actual_args}" msg += ", and #{performed_requests.count} with #{actual_args}"
end
end end
msg
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/shared' require 'telegram/bot/rspec/integration/shared'
RSpec.shared_context 'telegram/bot/integration/poller' do RSpec.shared_context 'telegram/bot/integration/poller' do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/shared' require 'telegram/bot/rspec/integration/shared'
require 'rack/test' require 'rack/test'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/shared' require 'telegram/bot/rspec/integration/shared'
RSpec.shared_context 'telegram/bot/integration/rails', type: :request do RSpec.shared_context 'telegram/bot/integration/rails', type: :request do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'active_support/json' require 'active_support/json'
require 'telegram/bot' require 'telegram/bot'
require 'telegram/bot/rspec/message_helpers' require 'telegram/bot/rspec/message_helpers'
@@ -10,5 +12,5 @@ RSpec.shared_context 'telegram/bot/integration/shared' do
let(:bot) { Telegram.bot } let(:bot) { Telegram.bot }
let(:clear_session?) { described_class.respond_to?(:session_store) } let(:clear_session?) { described_class.respond_to?(:session_store) }
before { described_class.session_store.try!(:clear) if clear_session? } before { described_class.session_store&.clear if clear_session? }
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Shared helpers for testing message updates. # Shared helpers for testing message updates.
RSpec.shared_context 'telegram/bot/message_helpers' do RSpec.shared_context 'telegram/bot/message_helpers' do
let(:default_message_options) { {from: from, chat: chat} } let(:default_message_options) { {from: from, chat: chat} }

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

@@ -7,7 +7,7 @@ module Telegram
def set_webhook def set_webhook
routes = Rails.application.routes.url_helpers routes = Rails.application.routes.url_helpers
cert_file = ENV['CERT'] cert_file = ENV.fetch('CERT', nil)
cert = File.open(cert_file) if cert_file cert = File.open(cert_file) if cert_file
each_bot do |key, bot| each_bot do |key, bot|
route_name = RoutesHelper.route_name_for_bot(bot) route_name = RoutesHelper.route_name_for_bot(bot)
@@ -16,7 +16,7 @@ module Telegram
bot.set_webhook( bot.set_webhook(
url: url, url: url,
certificate: cert, certificate: cert,
ip_address: ENV['IP_ADDRESS'], ip_address: ENV.fetch('IP_ADDRESS', nil),
drop_pending_updates: drop_pending_updates, drop_pending_updates: drop_pending_updates,
) )
end end
@@ -50,13 +50,13 @@ module Telegram
end end
def each_bot(&block) def each_bot(&block)
id = ENV['BOT'].try!(:to_sym) id = ENV['BOT']&.to_sym
bots = id ? {id => Client.by_id(id)} : Telegram.bots bots = id ? {id => Client.by_id(id)} : Telegram.bots
bots.each { |key, bot| bot.async(false) { block[key, bot] } } bots.each { |key, bot| bot.async(false) { block[key, bot] } }
end end
def drop_pending_updates def drop_pending_updates
ENV['DROP_PENDING_UPDATES'].try!(:downcase) == 'true' ENV['DROP_PENDING_UPDATES']&.downcase == 'true'
end end
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'abstract_controller' require 'abstract_controller'
require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/inflections'
require 'active_support/callbacks' require 'active_support/callbacks'
@@ -47,7 +49,7 @@ module Telegram
# ControllerClass.new(bot, from: telegram_user, chat: telegram_chat). # ControllerClass.new(bot, from: telegram_user, chat: telegram_chat).
# process(:help, *args) # process(:help, *args)
# #
class UpdatesController < AbstractController::Base # rubocop:disable ClassLength class UpdatesController < AbstractController::Base # rubocop:disable Metrics/ClassLength
abstract! abstract!
%w[ %w[
@@ -69,12 +71,12 @@ module Telegram
include AbstractController::Callbacks include AbstractController::Callbacks
# Redefine callbacks with default terminator. # Redefine callbacks with default terminator.
if ActiveSupport::VERSION::MAJOR >= 5 if ActiveSupport::VERSION::MAJOR >= 5
define_callbacks :process_action, define_callbacks :process_action,
skip_after_callbacks_if_terminated: true skip_after_callbacks_if_terminated: true
else else
define_callbacks :process_action, define_callbacks :process_action,
terminator: ->(_, result) { result == false }, terminator: ->(_, result) { result == false },
skip_after_callbacks_if_terminated: true skip_after_callbacks_if_terminated: true
end end
include Commands include Commands
@@ -87,11 +89,17 @@ module Telegram
extend Session::ConfigMethods extend Session::ConfigMethods
PAYLOAD_TYPES = %w[ PAYLOAD_TYPES = Set.new(%w[
message message
edited_message edited_message
channel_post channel_post
edited_channel_post edited_channel_post
business_connection
business_message
edited_business_message
deleted_business_messages
message_reaction
message_reaction_count
inline_query inline_query
chosen_inline_result chosen_inline_result
callback_query callback_query
@@ -99,7 +107,13 @@ module Telegram
pre_checkout_query pre_checkout_query
poll poll
poll_answer poll_answer
].freeze my_chat_member
chat_member
chat_join_request
chat_boost
removed_chat_boost
pre_checkout_query
].freeze)
class << self class << self
# Initialize controller and process update. # Initialize controller and process update.
@@ -108,41 +122,63 @@ module Telegram
end end
def payload_from_update(update) def payload_from_update(update)
update && PAYLOAD_TYPES.find do |type| case update
item = update[type] when nil then nil
return [item, type] if item when Hash
# faster lookup for the case when telegram-bot-types is not used
update.find do |type, item|
return [item, type] if PAYLOAD_TYPES.include?(type)
end
else
payload_from_typed_update(update)
end
end
private
def payload_from_typed_update(update)
PAYLOAD_TYPES.find do |type|
begin
item = update[type]
return [item, type] if item
rescue Exception # rubocop:disable Lint/RescueException
# dry-rb raises exception if field is not defined in schema
end
end end
end end
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 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 # keys or Hash with `:from` or `:chat` to override this values and assume
# that update is nil. # 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) # rubocop:disable Lint/MissingSuper
if update.is_a?(Hash) && (update.key?(:from) || update.key?(:chat)) if update.is_a?(Hash) && (update.key?(:from) || update.key?(:chat))
options = update options = update
update = nil update = nil
end end
@_update = update
@_bot = bot @_bot = bot
@_chat, @_from = options && options.values_at(:chat, :from) @_update = update
@_chat, @_from = options&.values_at(:chat, :from)
@_payload, @_payload_type = self.class.payload_from_update(update) @_payload, @_payload_type = self.class.payload_from_update(update)
@_webhook_request = webhook_request
end end
# Accessor to `'chat'` field of payload. Also tries `'chat'` in `'message'` # Accessor to `'chat'` field of payload. Also tries `'chat'` in `'message'`
# when there is no such field in payload. # when there is no such field in payload.
# #
# Can be overriden with `chat` option for #initialize. # Can be overriden with `chat` option for #initialize.
def chat def chat # rubocop:disable Metrics/PerceivedComplexity
@_chat ||= @_chat ||= # rubocop:disable Naming/MemoizedInstanceVariableName
if payload if payload
if payload.is_a?(Hash) if payload.is_a?(Hash)
payload['chat'] || payload['message'] && payload['message']['chat'] payload['chat'] || (payload['message'] && payload['message']['chat'])
else else
payload.try(:chat) || payload.try(:message).try!(:chat) payload.try(:chat) || payload.try(:message)&.chat
end end
end end
end end
@@ -150,7 +186,8 @@ 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.is_a?(Hash) ? payload['from'] : payload.try(:from) @_from ||= # rubocop:disable Naming/MemoizedInstanceVariableName
payload.is_a?(Hash) ? payload['from'] : payload.try(:from)
end end
# Processes current update. # Processes current update.
@@ -215,7 +252,7 @@ module Telegram
# 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)
logger.debug { "The action '#{action}' is not defined in #{self.class.name}" } if logger logger&.debug { "The action '#{action}' is not defined in #{self.class.name}" }
nil nil
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController

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

@@ -1,9 +1,11 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
# Support for parsing commands # Support for parsing commands
module Commands module Commands
CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i.freeze
class << self class << self
# Fetches command from text message. All subsequent words are returned # Fetches command from text message. All subsequent words are returned
@@ -34,7 +36,7 @@ module Telegram
def action_for_message def action_for_message
cmd, args = Commands.command_from_text(payload['text'], bot_username) cmd, args = Commands.command_from_text(payload['text'], bot_username)
return unless cmd return unless cmd
[[action_for_command(cmd), type: :command, command: cmd], args] [[action_for_command(cmd), {type: :command, command: cmd}], args]
end end
alias_method :action_for_channel_post, :action_for_message alias_method :action_for_channel_post, :action_for_message

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
@@ -23,8 +25,8 @@ module Telegram
def process_action(*args) def process_action(*args)
raw_payload = { raw_payload = {
controller: self.class.name, controller: self.class.name,
action: action_name, action: action_name,
update: update, update: update,
} }
Instrumentation.instrument(:start_processing, raw_payload.dup) Instrumentation.instrument(:start_processing, raw_payload.dup)
Instrumentation.instrument(:process_action, raw_payload) do |payload| Instrumentation.instrument(:process_action, raw_payload) do |payload|
@@ -61,14 +63,14 @@ module Telegram
# end # end
# #
# :api: plugin # :api: plugin
def cleanup_view_runtime #:nodoc: def cleanup_view_runtime # :nodoc:
yield yield
end end
# Every time after an action is processed, this method is invoked # Every time after an action is processed, this method is invoked
# with the payload, so you can add more information. # with the payload, so you can add more information.
# :api: plugin # :api: plugin
def append_info_to_payload(_payload) #:nodoc: def append_info_to_payload(_payload) # :nodoc:
end end
module ClassMethods module ClassMethods
@@ -76,7 +78,7 @@ module Telegram
# controller process action. This method should return an array # controller process action. This method should return an array
# with the messages to be added. # with the messages to be added.
# :api: plugin # :api: plugin
def log_process_action(_payload) #:nodoc: def log_process_action(_payload) # :nodoc:
[] []
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'active_support/log_subscriber' require 'active_support/log_subscriber'
module Telegram module Telegram
@@ -8,7 +10,7 @@ module Telegram
info do info do
payload = event.payload payload = event.payload
"Processing by #{payload[:controller]}##{payload[:action]}\n" \ "Processing by #{payload[:controller]}##{payload[:action]}\n" \
" Update: #{payload[:update].to_json}" " Update: #{payload[:update].to_json}"
end end
end end
@@ -17,7 +19,7 @@ module Telegram
payload = event.payload payload = event.payload
additions = UpdatesController.log_process_action(payload) additions = UpdatesController.log_process_action(payload)
message = "Completed in #{event.duration.round}ms" message = "Completed in #{event.duration.round}ms"
message << " (#{additions.join(' | ')})" if additions.present? message += " (#{additions.join(' | ')})" if additions.present?
message message
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
@@ -11,7 +13,7 @@ module Telegram
# respond_with :message, text: 'Where are you?' # respond_with :message, text: 'Where are you?'
# end # end
# #
# def set_location_from_messge(city = nil, *) # def set_location_from_message(city = nil, *)
# # update # # update
# end # end
# #
@@ -48,12 +50,12 @@ module Telegram
# it has higher priority than contextual action. # it has higher priority than contextual action.
def action_for_message def action_for_message
val = message_context_session.delete(:context) val = message_context_session.delete(:context)
context = val && val.to_s context = val&.to_s
super || context && begin super || (context && begin
args = payload['text'].try!(:split) || [] args = payload['text']&.split || []
action = action_for_message_context(context) action = action_for_message_context(context)
[[action, type: :message_context, context: context], args] [[action, {type: :message_context, context: context}], args]
end end)
end end
# Save context for the next request. # Save context for the next request.

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
@@ -59,9 +61,9 @@ module Telegram
# Edit message from callback query. # Edit message from callback query.
def edit_message(type, params = {}) def edit_message(type, params = {})
params = params =
if message_id = payload['inline_message_id'] # rubocop:disable AssignmentInCondition if message_id = payload['inline_message_id'] # rubocop:disable Lint/AssignmentInCondition
params.merge(inline_message_id: message_id) params.merge(inline_message_id: message_id)
elsif message = payload['message'] # rubocop:disable AssignmentInCondition elsif message = payload['message'] # rubocop:disable Lint/AssignmentInCondition
params.merge(chat_id: message['chat']['id'], message_id: message['message_id']) params.merge(chat_id: message['chat']['id'], message_id: message['message_id'])
else else
raise 'Can not edit message without `inline_message_id` or `message`' raise 'Can not edit message without `inline_message_id` or `message`'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'active_support/rescuable' require 'active_support/rescuable'
module Telegram module Telegram
@@ -11,7 +13,7 @@ module Telegram
def process_action(*) def process_action(*)
super super
rescue Exception => exception # rubocop:disable RescueException rescue Exception => exception # rubocop:disable Lint/RescueException
rescue_with_handler(exception) || raise rescue_with_handler(exception) || raise
end end
end end

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

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

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'rack/session/abstract/id' require 'rack/session/abstract/id'
require 'active_support/cache' require 'active_support/cache'
@@ -27,7 +29,8 @@ module Telegram
protected protected
def session def session
@_session ||= self.class.build_session(session_key) @_session ||= # rubocop:disable Naming/MemoizedInstanceVariableName
self.class.build_session(session_key)
end end
def session_key def session_key
@@ -41,7 +44,7 @@ module Telegram
class SessionHash < Rack::Session::Abstract::SessionHash class SessionHash < Rack::Session::Abstract::SessionHash
attr_reader :id attr_reader :id
def initialize(store, id) def initialize(store, id) # rubocop:disable Lint/MissingSuper
@store = store @store = store
@id = id @id = id
end end
@@ -65,13 +68,13 @@ module Telegram
def commit def commit
return unless loaded? return unless loaded?
data = to_hash.delete_if { |_, v| v.nil? } data = to_hash.compact
@store.write(id, data) @store.write(id, data)
end end
end end
class NullSessionHash < Session::SessionHash class NullSessionHash < Session::SessionHash
def initialize def initialize # rubocop:disable Lint/MissingSuper
@data = {} @data = {}
@loaded = true @loaded = true
@exists = true @exists = true

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
@@ -5,9 +7,9 @@ module Telegram
IVARS_TO_KEEP = %i[@_session].freeze IVARS_TO_KEEP = %i[@_session].freeze
# Perform multiple dispatches on same instance. # Perform multiple dispatches on same instance.
def dispatch_again(bot = nil, update = nil) def dispatch_again(bot = nil, update = nil, webhook_request = nil)
recycle! recycle!
initialize(bot, update) initialize(bot, update, webhook_request)
dispatch dispatch
end end
@@ -16,7 +18,7 @@ module Telegram
# everything will be rewriten with #initialize. # everything will be rewriten with #initialize.
# #
# With `full` set to `true` it'll clear all cached instance variables. # With `full` set to `true` it'll clear all cached instance variables.
def recycle!(full = false) def recycle!(full = false) # rubocop:disable Style/OptionalBooleanParameter
return unless full return unless full
(instance_variables - IVARS_TO_KEEP).each do |ivar| (instance_variables - IVARS_TO_KEEP).each do |ivar|
remove_instance_variable(ivar) remove_instance_variable(ivar)
@@ -27,7 +29,8 @@ module Telegram
# Stubs session. # Stubs session.
def session def session
@_session ||= Session::NullSessionHash.new @_session ||= # rubocop:disable Naming/MemoizedInstanceVariableName
Session::NullSessionHash.new
end end
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
@@ -12,15 +14,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 +30,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

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

@@ -1,10 +1,12 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
class UpdatesController class UpdatesController
# Include this module to type cast update to Virtus model # Include this module to type cast update to Virtus model
# using `telegram-bot-types` gem (install this gem first). # using `telegram-bot-types` gem (install this gem first).
module TypedUpdate 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) update = Types::Update.new(update) if update && !update.is_a?(Types::Update)
super super
end end

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

@@ -1,9 +1,11 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
# Supposed to be used in development environments only. # Supposed to be used in development environments only.
class UpdatesPoller class UpdatesPoller
class << self class << self
@@instances = {} # rubocop:disable ClassVars @@instances = {} # rubocop:disable Style/ClassVars
def instances def instances
@@instances @@instances
@@ -36,7 +38,7 @@ module Telegram
end end
def log(&block) def log(&block)
logger.info(&block) if logger logger&.info(&block)
end end
def start def start
@@ -56,7 +58,7 @@ module Telegram
def run def run
while running while running
updates = fetch_updates updates = fetch_updates
process_updates(updates) if updates && updates.any? process_updates(updates) if updates&.any?
end end
end end
@@ -82,8 +84,8 @@ module Telegram
process_update(update) process_update(update)
end end
end end
rescue StandardError => e rescue StandardError => exception
logger.error { ([e.message] + e.backtrace).join("\n") } if logger logger&.error { ([exception.message] + exception.backtrace).join("\n") }
end end
# Override this method to setup custom error collector. # Override this method to setup custom error collector.
@@ -102,10 +104,8 @@ module Telegram
end end
if defined?(Rails.application) && Rails.application.respond_to?(:reloader) if defined?(Rails.application) && Rails.application.respond_to?(:reloader)
def reloading_code def reloading_code(&block)
Rails.application.reloader.wrap do Rails.application.reloader.wrap(&block)
yield
end
end end
else else
def reloading_code def reloading_code

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

@@ -1,6 +1,8 @@
# frozen_string_literal: true
module Telegram module Telegram
module Bot module Bot
VERSION = '0.15.1'.freeze VERSION = '0.16.3'
def self.gem_version def self.gem_version
Gem::Version.new VERSION Gem::Version.new VERSION

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'integration_helper' require 'integration_helper'
RSpec.describe DefaultBotController, telegram_bot: :rails do RSpec.describe DefaultBotController, telegram_bot: :rails do
@@ -10,4 +12,13 @@ RSpec.describe DefaultBotController, telegram_bot: :rails do
subject { -> { dispatch_command :load_session } } subject { -> { dispatch_command :load_session } }
it { should_not raise_error } it { should_not raise_error }
end 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 end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'integration_helper' require 'integration_helper'
RSpec.describe NamedBotController, telegram_bot: :rails do RSpec.describe NamedBotController, telegram_bot: :rails do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'integration_helper' require 'integration_helper'
RSpec.describe OtherBotController, telegram_bot: :rails do RSpec.describe OtherBotController, telegram_bot: :rails do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/rails' require 'telegram/bot/rspec/integration/rails'
require 'action_controller' require 'action_controller'
require 'action_dispatch' require 'action_dispatch'

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

@@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'pathname' require 'pathname'
require 'pry' require 'debug'
require 'rspec/its' require 'rspec/its'
if ENV['CI'] if ENV['CI']
@@ -10,7 +12,7 @@ elsif ENV.key?('COV')
SimpleCov.start SimpleCov.start
end end
GEM_ROOT = Pathname.new File.expand_path('../..', __FILE__) GEM_ROOT = Pathname.new File.expand_path('..', __dir__)
$LOAD_PATH.unshift GEM_ROOT.join('lib') $LOAD_PATH.unshift GEM_ROOT.join('lib')
require 'telegram/bot' require 'telegram/bot'
@@ -18,7 +20,7 @@ require 'telegram/bot/updates_controller/rspec_helpers'
require 'telegram/bot/types' require 'telegram/bot/types'
require 'active_support/json' require 'active_support/json'
Dir[GEM_ROOT.join('spec/support/**/*.rb')].each { |f| require f } Dir[GEM_ROOT.join('spec/support/**/*.rb')].sort.each { |f| require f }
RSpec.configure do |config| RSpec.configure do |config|
config.expect_with :rspec do |expectations| config.expect_with :rspec do |expectations|

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

@@ -1,4 +1,6 @@
RSpec.shared_examples 'async' do |request_args: -> {}| # frozen_string_literal: true
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 +105,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 +114,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 +133,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,3 +1,5 @@
# frozen_string_literal: true
RSpec.shared_examples 'shared integration examples' do RSpec.shared_examples 'shared integration examples' do
let(:bot) { Telegram::Bot::ClientStub.new('token') } let(:bot) { Telegram::Bot::ClientStub.new('token') }
let(:controller_class) do let(:controller_class) do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::Async::Job do RSpec.describe Telegram::Bot::Async::Job do
let(:job_class) do let(:job_class) do
described_class = self.described_class described_class = self.described_class

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

@@ -0,0 +1,94 @@
# frozen_string_literal: true
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

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::Client::TypedResponse do RSpec.describe Telegram::Bot::Client::TypedResponse do
let(:bot) { Telegram::Bot::Client.new('token').tap { |x| x.extend described_class } } let(:bot) { Telegram::Bot::Client.new('token').tap { |x| x.extend described_class } }

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::Client do RSpec.describe Telegram::Bot::Client do
let(:instance) { described_class.new 'token' } let(:instance) { described_class.new 'token' }
let(:token) { 'token' } let(:token) { 'token' }
@@ -14,7 +16,10 @@ RSpec.describe Telegram::Bot::Client do
let(:input) { token } let(:input) { token }
it 'treats string as token' do 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 should eq result
end end
@@ -68,33 +73,16 @@ RSpec.describe Telegram::Bot::Client do
end end
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 describe '.prepare_async_args' do
subject { described_class.prepare_async_args(*input) } subject { described_class.prepare_async_args(*input) }
let(:input) { [:action, a: 1, b: :sym, c: [:other], 'd' => 'str'] } let(:input) { [:action, {a: 1, b: :sym, c: [:other], 'd' => 'str'}] }
it { should eq ['action', a: 1, b: 'sym', c: '["other"]', 'd' => 'str'] } it { should eq ['action', {a: 1, b: 'sym', c: '["other"]', 'd' => 'str'}] }
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(:token) { 'secret' }
let(:username) { 'superbot' } let(:username) { 'superbot' }
@@ -106,7 +94,7 @@ RSpec.describe Telegram::Bot::Client do
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 token } its(:token) { should eq token }
its(:username) { should eq username } its(:username) { should eq username }
its(:base_uri) { should eq "#{described_class::SERVER}/bot#{token}/" } its(:base_uri) { should eq "#{described_class::SERVER}/bot#{token}/" }
@@ -114,11 +102,12 @@ RSpec.describe Telegram::Bot::Client do
context 'with custom server' do context 'with custom server' do
let(:server) { 'http://my.server' } 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}/" } its(:base_uri) { should eq "#{server}/bot#{token}/" }
context 'and hash options' do 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}/" } its(:base_uri) { should eq "#{server}/bot#{token}/" }
end end
end end
@@ -142,7 +131,8 @@ RSpec.describe Telegram::Bot::Client do
let(:body) { body_json.to_json } let(:body) { body_json.to_json }
let(:body_json) { {'param' => 'val', 'description' => 'some description'} } let(:body_json) { {'param' => 'val', 'description' => 'some description'} }
before do 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 } expect(instance).to receive(:http_request).with(url, prepared_body) { response }
end end
around { |ex| Telegram::Bot::ClientStub.stub_all!(false) { ex.run } } around { |ex| Telegram::Bot::ClientStub.stub_all!(false) { ex.run } }

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

@@ -1,7 +1,9 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::ClientStub do RSpec.describe Telegram::Bot::ClientStub do
describe '#stub_all!' do describe '#stub_all!' do
let(:client) { Telegram::Bot::Client.new('token', 'bot_name') } let(:client) { Telegram::Bot::Client.new('token', 'bot_name') }
let(:clients) { ['token', token: 'token2'].map(&Telegram::Bot::Client.method(:wrap)) } let(:clients) { ['token', {token: 'token2'}].map(&Telegram::Bot::Client.method(:wrap)) }
shared_examples 'constructors' do |expected_class| shared_examples 'constructors' do |expected_class|
it 'makes Client.new return ClientStub' do it 'makes Client.new return ClientStub' do
@@ -37,11 +39,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 +55,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

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::ConfigMethods do RSpec.describe Telegram::Bot::ConfigMethods do
let(:registry) do let(:registry) do
Object.new.tap do |x| Object.new.tap do |x|

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'rack/mock' require 'rack/mock'
RSpec.describe Telegram::Bot::Middleware do RSpec.describe Telegram::Bot::Middleware do
@@ -11,7 +13,7 @@ RSpec.describe Telegram::Bot::Middleware do
let(:env) do let(:env) do
Rack::MockRequest.env_for('/', Rack::MockRequest.env_for('/',
method: :post, method: :post,
input: JSON.dump(update), input: JSON.dump(update),
'CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json',
) )
end end
@@ -24,7 +26,8 @@ RSpec.describe Telegram::Bot::Middleware do
end end
it 'calls dispatch on controller' do 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 subject
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/routes_helper' require 'telegram/bot/routes_helper'
RSpec.describe Telegram::Bot::RoutesHelper do RSpec.describe Telegram::Bot::RoutesHelper do
@@ -41,12 +43,12 @@ RSpec.describe Telegram::Bot::RoutesHelper do
end end
describe '#telegram_webhook' do 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(:mapper) { double(:mapper).tap { |x| x.extend described_class } }
let(:controller) { double(:controller, name: :controller) } let(:controller) { double(:controller, name: :controller) }
before { allow(Telegram).to receive(:bots) { bots } } before { allow(Telegram).to receive(:bots) { bots } }
def assert_route(bot, controller, path: nil, **expected_options) # rubocop:disable AbcSize def assert_route(bot, controller, path: nil, **expected_options) # rubocop:disable Metrics/AbcSize
path ||= "telegram/#{described_class.token_hash(bot.token)}" path ||= "telegram/#{described_class.token_hash(bot.token)}"
expect(mapper).to receive(:post) do |actual_path, actual_options| expect(mapper).to receive(:post) do |actual_path, actual_options|
expect(actual_path).to eq(path) expect(actual_path).to eq(path)

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/poller' require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Integration spec helpers', telegram_bot: :poller do RSpec.describe 'Integration spec helpers', telegram_bot: :poller do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::RSpec::ClientMatchers do RSpec.describe Telegram::Bot::RSpec::ClientMatchers do
let(:bot) { Telegram::Bot::ClientStub.new('TestBot') } let(:bot) { Telegram::Bot::ClientStub.new('TestBot') }
let(:other_bot) { Telegram::Bot::ClientStub.new('OtherTestBot') } let(:other_bot) { Telegram::Bot::ClientStub.new('OtherTestBot') }
@@ -31,6 +33,9 @@ RSpec.describe Telegram::Bot::RSpec::ClientMatchers do
args = {text: 'test', parseMode: :Markdown} args = {text: 'test', parseMode: :Markdown}
block = ->(*) { bot.send_message(args) } block = ->(*) { bot.send_message(args) }
expect(&block).to make_telegram_request(bot, :sendMessage).with(args) expect(&block).to make_telegram_request(bot, :sendMessage).with(args)
expect do
expect(&block).to make_telegram_request(bot, :sendMessage).with(args.merge(extra: 1))
end.to raise_error RSpec::Expectations::ExpectationNotMetError
expect(&block).to_not make_telegram_request(bot, :sendMessage).with(args.except(:text)) expect(&block).to_not make_telegram_request(bot, :sendMessage).with(args.except(:text))
expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage). expect { 3.times(&block) }.to make_telegram_request(bot, :sendMessage).
with(args).exactly(3).times with(args).exactly(3).times

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

@@ -1,5 +1,16 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/poller' require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Poller integration spec', telegram_bot: :poller do RSpec.describe 'Poller integration spec', telegram_bot: :poller do
include_examples 'shared integration examples' 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 end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/rack' require 'telegram/bot/rspec/integration/rack'
RSpec.describe 'Rack integration spec', telegram_bot: :rack do RSpec.describe 'Rack integration spec', telegram_bot: :rack do
@@ -17,4 +19,13 @@ RSpec.describe 'Rack integration spec', telegram_bot: :rack do
ActionDispatch::ParamsParser.new(app) ActionDispatch::ParamsParser.new(app)
end end
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 end

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

@@ -1 +1,2 @@
# frozen_string_literal: true
# Tested in spec/integration # Tested in spec/integration

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'telegram/bot/rspec/integration/poller' require 'telegram/bot/rspec/integration/poller'
RSpec.describe 'Integration: message helpers', telegram_bot: :poller do RSpec.describe 'Integration: message helpers', telegram_bot: :poller do
@@ -108,7 +110,7 @@ RSpec.describe 'Integration: message helpers', telegram_bot: :poller do
end end
context 'with options' do context 'with options' do
let(:args) { ['asd', 'qwe', option: 1] } let(:args) { ['asd', 'qwe', {option: 1}] }
it { should respond_with_message "Start: #{args[0...-1].inspect}, option: 1" } it { should respond_with_message "Start: #{args[0...-1].inspect}, option: 1" }
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
let(:controller_class) do let(:controller_class) do
@@ -6,7 +8,8 @@ RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
include described_class include described_class
attr_accessor :filter_done attr_accessor :filter_done
before_action only: :ctx2_callback_query do
before_action only: :ctx_2_callback_query do
self.filter_done = true self.filter_done = true
end end
@@ -14,12 +17,12 @@ RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
[:no_context, data] [:no_context, data]
end end
def ctx1_callback_query(data) def ctx_1_callback_query(data)
[:ctx1, data] [:ctx_1, data]
end end
def ctx2_callback_query(data) def ctx_2_callback_query(data)
[:ctx2, data] [:ctx_2, data]
end end
end end
end end
@@ -40,19 +43,19 @@ RSpec.describe Telegram::Bot::UpdatesController::CallbackQueryContext do
let(:data) { "#{ctx}:#{text}" } let(:data) { "#{ctx}:#{text}" }
context 'and valid' do context 'and valid' do
let(:ctx) { 'ctx1' } let(:ctx) { 'ctx_1' }
its(:call) { should eq [:ctx1, text] } its(:call) { should eq [:ctx_1, text] }
it { should_not change(controller, :filter_done) } it { should_not change(controller, :filter_done) }
context 'and context has callback' do context 'and context has callback' do
let(:ctx) { 'ctx2' } let(:ctx) { 'ctx_2' }
its(:call) { should eq [:ctx2, text] } its(:call) { should eq [:ctx_2, text] }
it { should change(controller, :filter_done) } it { should change(controller, :filter_done) }
end end
context 'and data has multiple colons' do context 'and data has multiple colons' do
let(:text) { super().tr(' ', ':') } let(:text) { super().tr(' ', ':') }
its(:call) { should eq [:ctx1, text] } its(:call) { should eq [:ctx_1, text] }
end end
end end

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::Commands do RSpec.describe Telegram::Bot::UpdatesController::Commands do
describe '#action_for_command' do describe '#action_for_command' do
subject { ->(*args) { object.action_for_command(*args) } } subject { ->(*args) { object.action_for_command(*args) } }
@@ -88,11 +90,11 @@ RSpec.describe Telegram::Bot::UpdatesController::Commands do
context 'with command' do context 'with command' do
let(:text) { "/test#{"@#{mention}" if mention} arg 1 2" } let(:text) { "/test#{"@#{mention}" if mention} arg 1 2" }
let(:mention) {} let(:mention) {}
it { should eq [['test!', type: :command, command: 'test'], %w[arg 1 2]] } it { should eq [['test!', {type: :command, command: 'test'}], %w[arg 1 2]] }
context 'with mention' do context 'with mention' do
let(:mention) { bot.username } let(:mention) { bot.username }
it { should eq [['test!', type: :command, command: 'test'], %w[arg 1 2]] } it { should eq [['test!', {type: :command, command: 'test'}], %w[arg 1 2]] }
end end
context 'with mention for other bot' do context 'with mention for other bot' do

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::Instrumentation do RSpec.describe Telegram::Bot::UpdatesController::Instrumentation do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::MessageContext do RSpec.describe Telegram::Bot::UpdatesController::MessageContext do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
let(:controller_class) do let(:controller_class) do
@@ -6,11 +8,13 @@ RSpec.describe Telegram::Bot::UpdatesController::MessageContext do
include described_class include described_class
attr_accessor :filter_done attr_accessor :filter_done
before_action only: :context_with_filter do before_action only: :context_with_filter do
self.filter_done = true self.filter_done = true
end end
attr_reader :callbacks_runs attr_reader :callbacks_runs
before_action { @callbacks_runs = (@callbacks_runs || 0) + 1 } before_action { @callbacks_runs = (@callbacks_runs || 0) + 1 }
def message(*args) def message(*args)

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController do RSpec.describe Telegram::Bot::UpdatesController do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
let(:params) { {arg: 1, 'other_arg' => 2} } let(:params) { {arg: 1, 'other_arg' => 2} }
@@ -38,7 +40,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
end end
context 'when update is not set' do context 'when update is not set' do
let(:controller_args) { [bot, chat: deep_stringify(chat)] } let(:controller_args) { [bot, {chat: deep_stringify(chat)}] }
it 'sets chat_id' do it 'sets chat_id' do
expect(bot).to receive("send_#{respond_type}"). expect(bot).to receive("send_#{respond_type}").
with(params.merge(chat_id: chat[:id])) { result } with(params.merge(chat_id: chat[:id])) { result }

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::Rescue do RSpec.describe Telegram::Bot::UpdatesController::Rescue do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::Session do RSpec.describe Telegram::Bot::UpdatesController::Session do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
let(:controller_class) do let(:controller_class) do

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

@@ -1,3 +1,7 @@
# frozen_string_literal: true
require 'action_dispatch'
RSpec.describe Telegram::Bot::UpdatesController::Testing do RSpec.describe Telegram::Bot::UpdatesController::Testing do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
let(:controller_class) do let(:controller_class) do
@@ -24,4 +28,27 @@ RSpec.describe Telegram::Bot::UpdatesController::Testing do
it { should_not change { controller.send(:session)[:key] }.from 'sval' } it { should_not change { controller.send(:session)[:key] }.from 'sval' }
end end
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 end

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

@@ -1,5 +1,28 @@
# frozen_string_literal: true
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 +33,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 +62,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

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController::TypedUpdate do RSpec.describe Telegram::Bot::UpdatesController::TypedUpdate do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
let(:controller_class) do let(:controller_class) do
@@ -9,14 +11,31 @@ 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
chat_join_request
# TODO: remove when added to telegram-bot-types
business_connection
business_message
edited_business_message
deleted_business_messages
message_reaction
message_reaction_count
chat_boost
removed_chat_boost
pre_checkout_query
]).
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 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|

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesController do RSpec.describe Telegram::Bot::UpdatesController do
include_context 'telegram/bot/updates_controller' include_context 'telegram/bot/updates_controller'
@@ -5,7 +7,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
subject { controller.action_for_payload } subject { controller.action_for_payload }
def stub_payload(*fields) def stub_payload(*fields)
Hash[fields.map { |x| [x, double(x)] }] fields.map { |x| [x, double(x)] }.to_h
end end
context 'when payload is inline_query' do context 'when payload is inline_query' do
@@ -46,6 +48,8 @@ RSpec.describe Telegram::Bot::UpdatesController do
chosen_inline_result chosen_inline_result
callback_query callback_query
poll_answer poll_answer
chat_join_request
pre_checkout_query
] ]
(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
@@ -89,7 +93,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
describe '#process' do describe '#process' do
subject { -> { controller.process(:action, *args) } } subject { -> { controller.process(:action, *args) } }
let(:args) { %i[arg1 arg2] } let(:args) { %i[arg_1 arg_2] }
let(:controller_class) do let(:controller_class) do
Class.new(described_class) do Class.new(described_class) do
attr_reader :acted, :hooked attr_reader :acted, :hooked
@@ -197,7 +201,7 @@ RSpec.describe Telegram::Bot::UpdatesController do
end end
context 'when options hash is given' do context 'when options hash is given' do
let(:controller_args) { [bot, from: from, chat: chat] } let(:controller_args) { [bot, {from: from, chat: chat}] }
with_reinitialize do with_reinitialize do
its(:bot) { should eq bot } its(:bot) { should eq bot }
its(:update) { should eq nil } its(:update) { should eq nil }

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot::UpdatesPoller do RSpec.describe Telegram::Bot::UpdatesPoller do
let(:instance) { described_class.new(bot, controller) } let(:instance) { described_class.new(bot, controller) }
let(:bot) { Telegram::Bot::Client.new('token') } let(:bot) { Telegram::Bot::Client.new('token') }

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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
RSpec.describe Telegram::Bot do RSpec.describe Telegram::Bot do
it 'has a version number' do it 'has a version number' do
expect(described_class::VERSION).not_to be nil expect(described_class::VERSION).not_to be nil

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

@@ -1,4 +1,6 @@
lib = File.expand_path('../lib', __FILE__) # frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'telegram/bot/version' require 'telegram/bot/version'
@@ -12,15 +14,19 @@ Gem::Specification.new do |spec|
spec.homepage = 'https://github.com/telegram-bot-rb/telegram-bot' spec.homepage = 'https://github.com/telegram-bot-rb/telegram-bot'
spec.license = 'MIT' spec.license = 'MIT'
spec.metadata = {
'rubygems_mfa_required' => 'true',
}
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) } spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
spec.bindir = 'exe' spec.bindir = 'exe'
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.required_ruby_version = '~> 2.0' spec.required_ruby_version = '>= 2.4'
spec.add_dependency 'actionpack', '>= 4.0', '< 6.2' spec.add_dependency 'actionpack', '>= 4.0', '< 7.2'
spec.add_dependency 'activesupport', '>= 4.0', '< 6.2' spec.add_dependency 'activesupport', '>= 4.0', '< 7.2'
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'