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

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

...

12 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
cd3d3739a4 v0.11.3 2017-05-05 17:30:49 +06:00
Max Melentiev
a054ac704c Build with rails 5.1 on travis 2017-05-05 17:28:59 +06:00
printercu
23aa92e614 Merge pull request #28 from ironbrains/master
Increase dependencies version
2017-05-05 17:19:05 +06:00
Bulat F
a49dca66fa Increase dependencies version
- Add support `activesupport 5.1`
- Add support `actionpack 5.1`
2017-05-05 10:16:30 +03:00
Max Melentiev
2d5ca981ce Rails 5.1 deep symbolized secrets support
Fixes #26
2017-04-22 12:02:59 +03:00
Max Melentiev
cf9bd87cf2 Add notice about how to require gem to Readme
Fixes #24
2017-03-11 21:33:20 +03:00
Max Melentiev
48ff7a4f43 Fix poller for typed responses
Fixes: #22
2017-03-05 17:56:32 +03:00
Max Melentiev
17260cb5a9 v0.11.0 2017-02-10 09:34:15 +03:00
Max Melentiev
817af95369 Remove Bot::StaleChat in favor of Bot::Forbidden 2017-02-10 09:33:41 +03:00
Max Melentiev
99fbe41b16 Fixed support for *channel_post updates 2016-12-09 13:33:55 +03:00
Max Melentiev
88178113d7 Support (edited_)channel_post updates, add api methods from 2.3, 2.3.1 2016-12-09 13:23:58 +03:00
printercu
1a2ac0dd83 Update README.md 2016-11-16 21:31:02 +03:00
20 изменённых файлов: 202 добавлений и 106 удалений

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

@@ -15,6 +15,7 @@ Style/DotPosition: {EnforcedStyle: trailing}
Style/FirstParameterIndentation: {EnforcedStyle: consistent} Style/FirstParameterIndentation: {EnforcedStyle: consistent}
Style/IfUnlessModifier: {Enabled: false} Style/IfUnlessModifier: {Enabled: false}
Style/ModuleFunction: {Enabled: false} Style/ModuleFunction: {Enabled: false}
Style/MultilineMethodCallIndentation: {EnforcedStyle: indented}
Style/MultilineOperationIndentation: {EnforcedStyle: indented} Style/MultilineOperationIndentation: {EnforcedStyle: indented}
Style/NestedParenthesizedCalls: {Enabled: false} Style/NestedParenthesizedCalls: {Enabled: false}
Style/PredicateName: {Enabled: false} Style/PredicateName: {Enabled: false}

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

@@ -5,5 +5,6 @@ rvm:
env: env:
- RAILS=4 - RAILS=4
- RAILS=5 - RAILS=5
- RAILS=5_1
notifications: notifications:
email: false email: false

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

@@ -1,3 +1,26 @@
# 0.11.3
- Release dependencies for Rails 5.1.
# 0.11.2
- Rails 5.1 deep symbolized secrets support.
# 0.11.1
- Fixed poller for typed response.
# 0.11.0
- Remove Bot::StaleChat in favor of Bot::Forbidden, as Telegram adds more
and more new descriptions.
Please open an issue if you face a problem.
# 0.10.2
- Support `(edited_)channel_post` updates.
- New methods from 2.3, 2.3.1 API updates.
# 0.10.0 # 0.10.0
- Integration helpers for RSpec. - Integration helpers for RSpec.

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

@@ -2,8 +2,10 @@ source 'https://rubygems.org'
gemspec gemspec
case ENV['RAILS'] case ENV['RAILS']
when '5_1'
gem 'actionpack', '5.1.0'
when '5' when '5'
gem 'actionpack', '5.0.0.rc1' gem 'actionpack', '5.0.2'
when '4' when '4'
gem 'actionpack', '~> 4.2' gem 'actionpack', '~> 4.2'
end end
@@ -13,7 +15,7 @@ group :development do
gem 'pry', '~> 0.10.1' gem 'pry', '~> 0.10.1'
gem 'pry-byebug', '~> 3.2.0' gem 'pry-byebug', '~> 3.2.0'
gem 'telegram-bot-types', '~> 0.2.0' gem 'telegram-bot-types', '~> 0.3.0'
gem 'rspec', '~> 3.5.0' gem 'rspec', '~> 3.5.0'
gem 'rspec-its', '~> 1.1.0' gem 'rspec-its', '~> 1.1.0'

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

@@ -24,6 +24,9 @@ Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bo
with session, keyboards and inline queries. 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 teamplate](https://github.com/telegram-bot-rb/rails_template)
to generate clean app in seconds.
## Installation ## Installation
Add this line to your application's Gemfile: Add this line to your application's Gemfile:
@@ -40,6 +43,12 @@ Or install it yourself as:
$ gem install telegram-bot $ gem install telegram-bot
Require if necessary:
```ruby
require 'telegram/bot'
```
## Usage ## Usage
### Configuration ### Configuration
@@ -97,7 +106,7 @@ bot.get_me.class # => Telegram::Bot::Types::User
``` ```
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::StaleChat` 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.
### Controller ### Controller

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

@@ -6,26 +6,7 @@ module Telegram
module Bot module Bot
class Error < StandardError; end class Error < StandardError; end
class NotFound < Error; end class NotFound < Error; end
class Forbidden < Error; end
# Error class for events when chat is not available anymore for bot.
# While Telegram has same error codes for different messages and there is no
# official docs for this error codes it uses `description` to
# check response.
class StaleChat < Error
DESCRIPTIONS = [
'Bot was blocked',
'bot was kicked',
"can't write to",
'group chat is deactivated',
].freeze
class << self
def match_response?(response)
description = response['description'].to_s
DESCRIPTIONS.any? { |x| description[x] }
end
end
end
autoload :Async, 'telegram/bot/async' autoload :Async, 'telegram/bot/async'
autoload :Botan, 'telegram/bot/botan' autoload :Botan, 'telegram/bot/botan'

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

@@ -46,44 +46,44 @@ module Telegram
@base_uri = format URL_TEMPLATE, self.token @base_uri = format URL_TEMPLATE, self.token
end end
def request(action, body = {}) # rubocop:disable PerceivedComplexity def request(action, body = {})
res = http_request("#{base_uri}#{action}", self.class.prepare_body(body)) res = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
status = res.status status = res.status
return JSON.parse(res.body) if 300 > status return JSON.parse(res.body) if 300 > status
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
err_msg = "#{res.reason}: #{result && result['description'] || '-'}" err_msg = result && result['description'] || '-'
if result if result
# NotFound is raised only for valid responses from Telegram # This errors are raised only for valid responses from Telegram
raise NotFound, err_msg if 404 == status case status
raise StaleChat, err_msg if StaleChat.match_response?(result) when 403 then raise Forbidden, err_msg
when 404 then raise NotFound, err_msg
end
end end
raise Error, err_msg raise Error, "#{res.reason}: #{err_msg}"
end end
# Splited to the sections similar to API docs.
%w( %w(
deleteWebhook
getUpdates
getWebhookInfo
setWebhook
answerCallbackQuery answerCallbackQuery
answerInlineQuery
editMessageCaption
editMessageReplyMarkup
editMessageText
forwardMessage forwardMessage
getChat getChat
getChatAdministrators getChatAdministrators
getChatMember getChatMember
getChatMembersCount getChatMembersCount
getFile getFile
getGameHighScores
getMe getMe
getUpdates
getUserProfilePhotos getUserProfilePhotos
getWebhookInfo
kickChatMember kickChatMember
leaveChat leaveChat
sendAudio sendAudio
sendChatAction sendChatAction
sendContact sendContact
sendDocument sendDocument
sendGame
sendLocation sendLocation
sendMessage sendMessage
sendPhoto sendPhoto
@@ -91,9 +91,17 @@ module Telegram
sendVenue sendVenue
sendVideo sendVideo
sendVoice sendVoice
setGameScore
setWebhook
unbanChatMember unbanChatMember
editMessageCaption
editMessageReplyMarkup
editMessageText
answerInlineQuery
getGameHighScores
sendGame
setGameScore
).each do |method| ).each do |method|
define_method(method.underscore) { |*args| request(method, *args) } define_method(method.underscore) { |*args| request(method, *args) }
end end

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

@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/transform_values' require 'active_support/core_ext/hash/transform_values'
require 'active_support/core_ext/hash/indifferent_access'
module Telegram module Telegram
module Bot module Bot
@@ -48,9 +49,10 @@ module Telegram
def bots_config def bots_config
@bots_config ||= @bots_config ||=
if defined?(Rails.application) if defined?(Rails.application)
telegram_config = Rails.application.secrets[:telegram] || {} secrets = Rails.application.secrets.
(telegram_config['bots'] || {}).symbolize_keys.tap do |config| fetch(:telegram, {}).with_indifferent_access
default = telegram_config['bot'] secrets.fetch(:bots, {}).symbolize_keys.tap do |config|
default = secrets[:bot]
config[:default] = default if default config[:default] = default if default
end end
else else

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

@@ -139,9 +139,9 @@ module Telegram
# Helper for asserting message is sent. Note that options are checked # Helper for asserting message is sent. Note that options are checked
# with `hash_including`. For strict checks use #make_telegram_request. # with `hash_including`. For strict checks use #make_telegram_request.
def send_telegram_message(bot, text = nil, options = {}) def send_telegram_message(bot, text = nil, options = {})
description = "send telegram message #{text.inspect}"
text = a_string_matching(text) if text.is_a?(Regexp) text = a_string_matching(text) if text.is_a?(Regexp)
options = options.merge(text: text) if text options = options.merge(text: text) if text
description = "send telegram message #{text.inspect}"
MakeTelegramRequest.new(bot, :sendMessage, description: description). MakeTelegramRequest.new(bot, :sendMessage, description: description).
with(hash_including(options)) with(hash_including(options))
end end

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

@@ -50,7 +50,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 class UpdatesController < AbstractController::Base # rubocop:disable ClassLength
abstract! abstract!
require 'telegram/bot/updates_controller/session' require 'telegram/bot/updates_controller/session'
@@ -80,10 +80,12 @@ module Telegram
PAYLOAD_TYPES = %w( PAYLOAD_TYPES = %w(
message message
edited_message
channel_post
edited_channel_post
inline_query inline_query
chosen_inline_result chosen_inline_result
callback_query callback_query
edited_message
).freeze ).freeze
CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i
CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)") CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)")
@@ -181,11 +183,13 @@ module Telegram
cmd &&= self.class.action_for_command(cmd) cmd &&= self.class.action_for_command(cmd)
[true, cmd, args] if cmd [true, cmd, args] if cmd
end end
alias_method :action_for_channel_post, :action_for_message
# It doesn't extract commands from edited messages. Just process # It doesn't extract commands from edited messages. Just process
# them as usual ones. # them as usual ones.
def action_for_edited_message def action_for_edited_message
end end
alias_method :action_for_edited_channel_post, :action_for_edited_message
def action_for_inline_query def action_for_inline_query
[false, payload_type, [payload['query'], payload['offset']]] [false, payload_type, [payload['query'], payload['offset']]]

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

@@ -65,14 +65,16 @@ module Telegram
def fetch_updates def fetch_updates
response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) } response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) }
return unless response['ok'] && response['result'].any? updates = response.is_a?(Array) ? response : response['result']
return unless updates && updates.any?
reload! do reload! do
response['result'].each do |update| updates.each do |update|
@offset = update['update_id'] + 1 @offset = update['update_id'] + 1
yield update yield update
end end
end end
rescue Timeout::Error # rubocop:disable HandleExceptions rescue Timeout::Error
log { 'Fetch timeout' }
end end
def reload! def reload!

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

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

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

@@ -16,6 +16,7 @@ $LOAD_PATH.unshift GEM_ROOT.join('lib')
require 'telegram/bot' require 'telegram/bot'
require 'telegram/bot/updates_controller/rspec_helpers' require 'telegram/bot/updates_controller/rspec_helpers'
require 'telegram/bot/types' require 'telegram/bot/types'
require 'active_support/core_ext/object/json'
Dir[GEM_ROOT.join('spec/support/**/*.rb')].each { |f| require f } Dir[GEM_ROOT.join('spec/support/**/*.rb')].each { |f| require f }

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

@@ -3,7 +3,7 @@ RSpec.describe Telegram::Bot::Botan do
let(:token) { 'token' } let(:token) { 'token' }
include_examples 'initializers', :botans include_examples 'initializers', :botans
include_examples 'async', request_args: -> { [double(:method), double(:url)] } it_behaves_like 'async', request_args: -> { [double(:method), double(:url)] }
describe '.new' do describe '.new' do
subject { described_class.new(*args) } subject { described_class.new(*args) }

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

@@ -4,7 +4,7 @@ RSpec.describe Telegram::Bot::Client do
let(:botan_token) { double(:botan_token) } let(:botan_token) { double(:botan_token) }
include_examples 'initializers' include_examples 'initializers'
include_examples 'async', request_args: -> { [double(:action), {body: :content}] } it_behaves_like 'async', request_args: -> { [double(:action), {body: :content}] }
describe '.prepare_body' do describe '.prepare_body' do
subject { described_class.prepare_body(input) } subject { described_class.prepare_body(input) }
@@ -48,4 +48,49 @@ RSpec.describe Telegram::Bot::Client do
its(:base_uri) { should include args[0][:token] } its(:base_uri) { should include args[0][:token] }
end end
end end
describe '#request' do
subject { -> { instance.request(action, request_body) } }
let(:action) { :some_action }
let(:url) { "#{format described_class::URL_TEMPLATE, token}#{action}" }
let(:request_body) { double(:body) }
let(:prepared_body) { double(:prepared_body) }
let(:response) { HTTP::Message.new_response(body).tap { |x| x.status = status } }
let(:status) { 200 }
let(:body) { body_json.to_json }
let(:body_json) { {'param' => 'val', 'description' => 'some description'} }
before do
expect(described_class).to receive(:prepare_body).with(request_body) { prepared_body }
expect(instance).to receive(:http_request).with(url, prepared_body) { response }
end
around { |ex| Telegram::Bot::ClientStub.stub_all!(false) { ex.run } }
shared_examples 'invalid body' do |error = Telegram::Bot::Error|
context 'when body is not json' do
let(:body) { '{' }
it { should raise_error error }
end
end
its(:call) { should eq body_json }
include_examples 'invalid body', JSON::ParserError
context 'when status is 403' do
let(:status) { 403 }
it { should raise_error Telegram::Bot::Forbidden, body_json['description'] }
include_examples 'invalid body'
end
context 'when status is 404' do
let(:status) { 404 }
it { should raise_error Telegram::Bot::NotFound, body_json['description'] }
include_examples 'invalid body'
end
context 'when status is other' do
let(:status) { 500 }
it { should raise_error Telegram::Bot::Error, /#{body_json['description']}/ }
end
end
end end

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

@@ -87,6 +87,12 @@ RSpec.describe Telegram::Bot::ConfigMethods do
end end
it { should include default: config[:bot] } it { should include default: config[:bot] }
it { should include config[:bots] } it { should include config[:bots] }
context 'on rails >5.1 (deep symbolized keys)' do
let(:secrets) { {telegram: config.deep_symbolize_keys} }
it { should include default: config[:bot] }
it { should include config[:bots] }
end
end end
end end
end end

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

@@ -102,42 +102,44 @@ RSpec.describe Telegram::Bot::UpdatesController do
it { should eq [false, payload_type, payload.values_at(:data)] } it { should eq [false, payload_type, payload.values_at(:data)] }
end end
context 'when payload is edited_message' do
let(:payload_type) { 'edited_message' }
it { should eq [false, payload_type, [payload]] }
end
context 'when payload is not supported' do context 'when payload is not supported' do
let(:payload_type) { '_unsupported_' } let(:payload_type) { '_unsupported_' }
it { should eq [false, :unsupported_payload_type, []] } it { should eq [false, :unsupported_payload_type, []] }
end end
context 'when payload is message' do %w(message channel_post).each do |type|
let(:payload_type) { 'message' } context 'when payload is edited_message' do
let(:payload) { {'text' => text} } let(:payload_type) { "edited_#{type}" }
let(:text) { 'test' } it { should eq [false, payload_type, [payload]] }
it { should eq [false, payload_type, [payload]] }
context 'with command' do
let(:text) { "/test#{"@#{mention}" if mention} arg 1 2" }
let(:mention) {}
it { should eq [true, 'test', %w(arg 1 2)] }
context 'with mention' do
let(:mention) { bot.username }
it { should eq [true, 'test', %w(arg 1 2)] }
end
context 'with mention for other bot' do
let(:mention) { other_bot_name }
it { should eq [false, 'message', [payload]] }
end
end end
context 'without text' do context 'when payload is message' do
let(:payload) { {'audio' => {'file_id' => 123}} } let(:payload_type) { type }
let(:payload) { {'text' => text} }
let(:text) { 'test' }
it { should eq [false, payload_type, [payload]] } it { should eq [false, payload_type, [payload]] }
context 'with command' do
let(:text) { "/test#{"@#{mention}" if mention} arg 1 2" }
let(:mention) {}
it { should eq [true, 'test', %w(arg 1 2)] }
context 'with mention' do
let(:mention) { bot.username }
it { should eq [true, 'test', %w(arg 1 2)] }
end
context 'with mention for other bot' do
let(:mention) { other_bot_name }
it { should eq [false, payload_type, [payload]] }
end
end
context 'without text' do
let(:payload) { {'audio' => {'file_id' => 123}} }
it { should eq [false, payload_type, [payload]] }
end
end end
end end
end end

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

@@ -1,9 +1,39 @@
RSpec.describe Telegram::Bot::UpdatesPoller do RSpec.describe Telegram::Bot::UpdatesPoller do
describe '#initialize' do let(:instance) { described_class.new(bot, controller) }
subject { described_class.new bot, controller } let(:bot) { Telegram::Bot::Client.new('token') }
let(:bot) { double } let(:controller) { double(:controller) }
let(:controller) { double }
describe '#initialize' do
subject { instance }
it { should be } it { should be }
end end
describe '#fetch_updates' do
subject { -> { instance.fetch_updates(&block) } }
let(:block) { ->(x) { expect(x).to eq expected_results.shift } }
let(:results) { [{update_id: 12}, {update_id: 34}] }
let(:expected_results) { results.as_json }
let(:request_result) { {ok: true, result: results}.as_json }
before do
allow(bot).to receive(:get_updates) do
expect(bot.async).to be_falsy
request_result
end
end
it { should change(instance, :offset).to(results.last[:update_id] + 1) }
it { should change { expected_results }.to([]) }
context 'with typed response' do
let(:request_result) { results.as_json.map { |x| Telegram::Bot::Types::Update.new(x) } }
let(:expected_results) { request_result.dup }
it { should change(instance, :offset).to(results.last[:update_id] + 1) }
it { should change { expected_results }.to([]) }
end
context 'when bot is in async mode' do
let(:bot) { Telegram::Bot::Client.new('token', async: Class.new) }
it { should change { expected_results }.to([]) }
end
end
end end

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

@@ -2,25 +2,4 @@ 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
end end
describe described_class::StaleChat do
describe '.match_response?' do
subject { ->(val) { described_class.match_response?(val) } }
it 'returns true for specific errors' do
expect(subject.call({})).to eq false
expect(subject.call('description' => 'test')).to eq false
expect(subject.call('description' => 'Error: bot was kicked from')).to eq true
expect(subject.call(
'description' => "Forbidden: can't write to private chat with deleted user"
)).to eq true
expect(subject.call(
'description' => 'Bad request: group chat is deactivated'
)).to eq true
expect(subject.call(
'description' => 'Forbidden: Bot was blocked by the user'
)).to eq true
end
end
end
end end

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

@@ -20,8 +20,8 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = '~> 2.0' spec.required_ruby_version = '~> 2.0'
spec.add_dependency 'activesupport', '>= 4.0', '< 5.1' spec.add_dependency 'activesupport', '>= 4.0', '<= 5.1'
spec.add_dependency 'actionpack', '>= 4.0', '< 5.1' spec.add_dependency 'actionpack', '>= 4.0', '<= 5.1'
spec.add_dependency 'httpclient', '~> 2.7' spec.add_dependency 'httpclient', '~> 2.7'
spec.add_development_dependency 'bundler', '~> 1.11' spec.add_development_dependency 'bundler', '~> 1.11'
spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rake', '~> 10.0'