зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-08-28 15:26:18 +03:00
Fix sending File objects in nested objects in sendMediaGroup
Этот коммит содержится в:
@@ -1,6 +1,7 @@
|
||||
# Unreleased
|
||||
|
||||
- Rails 7.0 support.
|
||||
- Fix sending File objects in nested objects in sendMediaGroup.
|
||||
|
||||
# 0.15.4
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'active_support/core_ext/hash/keys'
|
||||
require 'json'
|
||||
require 'httpclient'
|
||||
|
||||
module Telegram
|
||||
@@ -8,7 +7,9 @@ module Telegram
|
||||
SERVER = 'https://api.telegram.org'.freeze
|
||||
URL_TEMPLATE = '%<server>s/bot%<token>s/'.freeze
|
||||
|
||||
autoload :RequestBodyFormatter, 'telegram/bot/client/request_body_formatter'
|
||||
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
||||
|
||||
prepend Async
|
||||
include DebugClient
|
||||
|
||||
@@ -35,16 +36,8 @@ module Telegram
|
||||
prepend TypedResponse
|
||||
end
|
||||
|
||||
# Encodes nested hashes as json.
|
||||
def prepare_body(body)
|
||||
body = body.dup
|
||||
body.each do |k, val|
|
||||
body[k] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_async_args(action, body = {})
|
||||
[action.to_s, Async.prepare_hash(prepare_body(body))]
|
||||
[action.to_s, Async.prepare_hash(RequestBodyFormatter.format(body))]
|
||||
end
|
||||
|
||||
def error_for_response(response)
|
||||
@@ -70,7 +63,7 @@ module Telegram
|
||||
end
|
||||
|
||||
def request(action, body = {})
|
||||
response = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
|
||||
response = http_request("#{base_uri}#{action}", RequestBodyFormatter.format(body))
|
||||
raise self.class.error_for_response(response) if response.status >= 300
|
||||
JSON.parse(response.body)
|
||||
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
|
||||
|
||||
describe '.prepare_body' do
|
||||
subject { described_class.prepare_body(input) }
|
||||
|
||||
context 'when plain hash is given' do
|
||||
let(:input) { {a: 1, b: '2', c: nil} }
|
||||
it { should eq input }
|
||||
end
|
||||
|
||||
context 'when nested hash is given' do
|
||||
let(:input) { {a: 1, b: '2', c: [1, 2], d: {a: 1}, e: {b: []}} }
|
||||
|
||||
it 'encodes nested hashes to json' do
|
||||
expected = input.dup
|
||||
%i[c d e].each { |x| expected[x] = expected[x].to_json }
|
||||
should eq expected
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.prepare_async_args' do
|
||||
subject { described_class.prepare_async_args(*input) }
|
||||
let(:input) { [:action, a: 1, b: :sym, c: [:other], 'd' => 'str'] }
|
||||
|
||||
Ссылка в новой задаче
Block a user