зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-09-07 19:35:52 +03:00
Fix sending File objects in nested objects in sendMediaGroup
Этот коммит содержится в:
@@ -1,6 +1,7 @@
|
|||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- Rails 7.0 support.
|
- Rails 7.0 support.
|
||||||
|
- Fix sending File objects in nested objects in sendMediaGroup.
|
||||||
|
|
||||||
# 0.15.4
|
# 0.15.4
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
require 'active_support/core_ext/hash/keys'
|
require 'active_support/core_ext/hash/keys'
|
||||||
require 'json'
|
|
||||||
require 'httpclient'
|
require 'httpclient'
|
||||||
|
|
||||||
module Telegram
|
module Telegram
|
||||||
@@ -8,7 +7,9 @@ module Telegram
|
|||||||
SERVER = 'https://api.telegram.org'.freeze
|
SERVER = 'https://api.telegram.org'.freeze
|
||||||
URL_TEMPLATE = '%<server>s/bot%<token>s/'.freeze
|
URL_TEMPLATE = '%<server>s/bot%<token>s/'.freeze
|
||||||
|
|
||||||
|
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,16 +36,8 @@ 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))]
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_for_response(response)
|
def error_for_response(response)
|
||||||
@@ -70,7 +63,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))
|
||||||
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
|
||||||
|
|||||||
48
lib/telegram/bot/client/request_body_formatter.rb
Обычный файл
48
lib/telegram/bot/client/request_body_formatter.rb
Обычный файл
@@ -0,0 +1,48 @@
|
|||||||
|
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
|
||||||
|
if action.to_s == 'sendMediaGroup'
|
||||||
|
body = extract_files_from_array!(body, :media)
|
||||||
|
end
|
||||||
|
body.each do |key, val|
|
||||||
|
body[key] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def extract_files_from_array!(body, field_name)
|
||||||
|
field_name = [field_name.to_sym, field_name.to_s].find { |x| body.key?(x) }
|
||||||
|
return body unless field_name && body[field_name].is_a?(Array)
|
||||||
|
files = {}
|
||||||
|
body[field_name] = body[field_name].map { |x| extract_files_from_hash(x, files) }
|
||||||
|
body.merge!(files)
|
||||||
|
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
|
||||||
62
spec/telegram/bot/client/request_body_formatter_spec.rb
Обычный файл
62
spec/telegram/bot/client/request_body_formatter_spec.rb
Обычный файл
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -71,25 +71,6 @@ 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'] }
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user