Class: MaxBotApi::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/max_bot_api/client.rb

Overview

Main API client. Holds auth config and provides resource accessors.

Constant Summary collapse

DEFAULT_BASE_URL =

Default API base URL.

'https://platform-api.max.ru/'
DEFAULT_VERSION =

Default API version appended as query param.

'1.2.5'
SECRET_HEADER =

Webhook secret header name.

'X-Max-Bot-Api-Secret'
DEFAULT_PAUSE =

Default pause between update polling loops.

1
DEFAULT_UPDATES_LIMIT =

Default limit for updates requests.

50
MAX_RETRIES =

Max retry attempts for update polling.

3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token:, base_url: DEFAULT_BASE_URL, version: DEFAULT_VERSION, faraday: nil, adapter: Faraday.default_adapter) ⇒ Client

Returns a new instance of Client.

Parameters:

  • token (String)

    bot token

  • base_url (String) (defaults to: DEFAULT_BASE_URL)

    API base URL

  • version (String) (defaults to: DEFAULT_VERSION)

    API version

  • faraday (Faraday::Connection, nil) (defaults to: nil)

    custom Faraday connection

  • adapter (Symbol) (defaults to: Faraday.default_adapter)

    Faraday adapter

Raises:



31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/max_bot_api/client.rb', line 31

def initialize(token:, base_url: DEFAULT_BASE_URL, version: DEFAULT_VERSION, faraday: nil,
               adapter: Faraday.default_adapter)
  raise EmptyTokenError, 'bot token is empty' if token.to_s.empty?

  @token = token
  @base_url = normalize_base_url(base_url)
  @version = version.to_s.empty? ? DEFAULT_VERSION : version.to_s

  @conn = faraday || Faraday.new(url: @base_url) do |f|
    f.request :multipart
    f.request :url_encoded
    f.adapter adapter
  end
end

Instance Attribute Details

#base_urlString (readonly)

Returns:

  • (String)

    bot token

  • (String)

    base URL

  • (String)

    API version



24
25
26
# File 'lib/max_bot_api/client.rb', line 24

def base_url
  @base_url
end

#tokenString (readonly)

Returns:

  • (String)

    bot token

  • (String)

    base URL

  • (String)

    API version



24
25
26
# File 'lib/max_bot_api/client.rb', line 24

def token
  @token
end

#versionString (readonly)

Returns:

  • (String)

    bot token

  • (String)

    base URL

  • (String)

    API version



24
25
26
# File 'lib/max_bot_api/client.rb', line 24

def version
  @version
end

Instance Method Details

#botsObject



46
47
48
# File 'lib/max_bot_api/client.rb', line 46

def bots
  Resources::Bots.new(self)
end

#chatsObject



50
51
52
# File 'lib/max_bot_api/client.rb', line 50

def chats
  Resources::Chats.new(self)
end

#debugs(chat_id: 0) ⇒ Object

Build a debug sender bound to a chat.

Parameters:

  • chat_id (Integer) (defaults to: 0)


68
69
70
# File 'lib/max_bot_api/client.rb', line 68

def debugs(chat_id: 0)
  Resources::Debugs.new(self, chat_id: chat_id)
end

#each_update(pause: DEFAULT_PAUSE, limit: DEFAULT_UPDATES_LIMIT, timeout: nil, types: nil, debug: false, &block) ⇒ Object

Iterates over updates, yielding each update hash.



125
126
127
128
129
# File 'lib/max_bot_api/client.rb', line 125

def each_update(pause: DEFAULT_PAUSE, limit: DEFAULT_UPDATES_LIMIT, timeout: nil, types: nil, debug: false, &block)
  return updates_enum(pause: pause, limit: limit, timeout: timeout, types: types, debug: debug) unless block

  updates_enum(pause: pause, limit: limit, timeout: timeout, types: types, debug: debug).each(&block)
end

#get_updates(limit: nil, timeout: nil, marker: nil, types: nil, debug: false) ⇒ Object

Fetch updates from the API.

Parameters:

  • limit (Integer, nil) (defaults to: nil)
  • timeout (Integer, nil) (defaults to: nil)
  • marker (Integer, nil) (defaults to: nil)
  • types (Array<String>, nil) (defaults to: nil)
  • debug (Boolean) (defaults to: false)


78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/max_bot_api/client.rb', line 78

def get_updates(limit: nil, timeout: nil, marker: nil, types: nil, debug: false)
  query = {}
  query['limit'] = limit if limit && limit.to_i > 0
  query['timeout'] = timeout.to_i if timeout && timeout.to_i > 0
  query['marker'] = marker if marker && marker.to_i > 0
  Array(types).each { |t| (query['types'] ||= []) << t }

  result = request(:get, 'updates', query: query)
  Updates::Parser.parse_update_list(result, debug: debug)
rescue TimeoutError
  { updates: [], marker: nil }
end

#get_updates_with_retry(limit: nil, timeout: nil, marker: nil, types: nil, debug: false) ⇒ Object

Fetch updates with retry/backoff.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/max_bot_api/client.rb', line 92

def get_updates_with_retry(limit: nil, timeout: nil, marker: nil, types: nil, debug: false)
  last_error = nil

  MAX_RETRIES.times do |attempt|
    return get_updates(limit: limit, timeout: timeout, marker: marker, types: types, debug: debug)
  rescue Error => e
    last_error = e
    raise e if attempt == MAX_RETRIES - 1

    sleep(2**attempt)
  end

  raise last_error
end

#http_clientObject



184
185
186
# File 'lib/max_bot_api/client.rb', line 184

def http_client
  @conn
end

#messagesObject



54
55
56
# File 'lib/max_bot_api/client.rb', line 54

def messages
  Resources::Messages.new(self)
end

#parse_webhook(body, debug: false) ⇒ Object

Parses a single webhook payload into an update hash.



132
133
134
# File 'lib/max_bot_api/client.rb', line 132

def parse_webhook(body, debug: false)
  Updates::Parser.parse_update(body.to_s, debug: debug)
end

#request(method, path, query: nil, body: nil, headers: {}, reset: false) ⇒ Object

Perform an HTTP request.

Parameters:

  • method (Symbol)
  • path (String)
  • query (Hash) (defaults to: nil)
  • body (Hash, Array, String, nil) (defaults to: nil)
  • headers (Hash) (defaults to: {})
  • reset (Boolean) (defaults to: false)


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/max_bot_api/client.rb', line 150

def request(method, path, query: nil, body: nil, headers: {}, reset: false)
  query = (query || {}).dup
  query['v'] = version

  response = @conn.public_send(method) do |req|
    req.url(path.to_s.sub(%r{\A/}, ''))
    req.params.update(query) unless query.empty?
    req.headers['User-Agent'] = "max-bot-api-client-ruby/#{VERSION}"
    req.headers['Authorization'] = token unless reset
    headers.each { |k, v| req.headers[k] = v }

    if body
      if body.is_a?(Hash) || body.is_a?(Array)
        if multipart_body?(body)
          req.body = body
        else
          req.headers['Content-Type'] ||= 'application/json'
          req.body = JSON.generate(body)
        end
      else
        req.body = body
      end
    end
  end

  handle_response(response)
rescue Faraday::TimeoutError => e
  raise TimeoutError.new(op: "#{method.to_s.upcase} #{path}", reason: e.message)
rescue Faraday::ConnectionFailed, Faraday::SSLError, Faraday::Error => e
  raise NetworkError.new(op: "#{method.to_s.upcase} #{path}", original_error: e)
rescue JSON::GeneratorError => e
  raise SerializationError.new(op: 'marshal', type: 'request body', original_error: e)
end

#subscriptionsObject



58
59
60
# File 'lib/max_bot_api/client.rb', line 58

def subscriptions
  Resources::Subscriptions.new(self)
end

#updates_enum(pause: DEFAULT_PAUSE, limit: DEFAULT_UPDATES_LIMIT, timeout: nil, types: nil, debug: false) ⇒ Object

Returns an enumerator that yields updates indefinitely.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/max_bot_api/client.rb', line 108

def updates_enum(pause: DEFAULT_PAUSE, limit: DEFAULT_UPDATES_LIMIT, timeout: nil, types: nil, debug: false)
  Enumerator.new do |yielder|
    marker = nil
    loop do
      updates_list = get_updates_with_retry(limit: limit, timeout: timeout, marker: marker, types: types,
                                            debug: debug)
      updates = Array(updates_list[:updates])

      updates.each { |update| yielder << update }
      marker = updates_list[:marker] if updates_list[:marker]

      sleep(pause)
    end
  end
end

#uploadsObject



62
63
64
# File 'lib/max_bot_api/client.rb', line 62

def uploads
  Resources::Uploads.new(self)
end

#webhook_secret_valid?(headers:, secret:) ⇒ Boolean

Validates the webhook secret header against the expected secret.

Parameters:

  • headers (Hash)
  • secret (String)

Returns:

  • (Boolean)


139
140
141
# File 'lib/max_bot_api/client.rb', line 139

def webhook_secret_valid?(headers:, secret:)
  header_value(headers, SECRET_HEADER) == secret.to_s
end