Class: MisarBlog::Client

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, base_url: BASE_URL, timeout: 30, max_retries: 3) ⇒ Client

Returns a new instance of Client.

Parameters:

  • api_key (String)

    a mbk_... developer key or an OAuth 2.1 access token

  • base_url (String) (defaults to: BASE_URL)

    override the API base (default BASE_URL)

  • timeout (Integer) (defaults to: 30)

    per-request read timeout in seconds

  • max_retries (Integer) (defaults to: 3)

    total attempts for retryable failures (>= 1)

Raises:

  • (ArgumentError)


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/misarblog/client.rb', line 246

def initialize(api_key:, base_url: BASE_URL, timeout: 30, max_retries: 3)
  raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?

  @api_key     = api_key
  @base_url    = base_url.chomp("/")
  @timeout     = timeout
  @max_retries = [max_retries, 1].max

  @ai        = AiResource.new(self)
  @articles  = ArticlesResource.new(self)
  @images    = ImagesResource.new(self)
  @reactions = ReactionsResource.new(self)
  @series    = SeriesResource.new(self)
  @account   = AccountResource.new(self)
  @analytics = AnalyticsResource.new(self)
  @comments  = CommentsResource.new(self)
  @follows   = FollowsResource.new(self)
end

Instance Attribute Details

#accountObject (readonly)

Returns the value of attribute account.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def 
  @account
end

#aiObject (readonly)

Returns the value of attribute ai.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def ai
  @ai
end

#analyticsObject (readonly)

Returns the value of attribute analytics.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def analytics
  @analytics
end

#articlesObject (readonly)

Returns the value of attribute articles.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def articles
  @articles
end

#commentsObject (readonly)

Returns the value of attribute comments.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def comments
  @comments
end

#followsObject (readonly)

Returns the value of attribute follows.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def follows
  @follows
end

#imagesObject (readonly)

Returns the value of attribute images.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def images
  @images
end

#reactionsObject (readonly)

Returns the value of attribute reactions.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def reactions
  @reactions
end

#seriesObject (readonly)

Returns the value of attribute series.



239
240
241
# File 'lib/misarblog/client.rb', line 239

def series
  @series
end

Instance Method Details

#request(method, path, data = {}) ⇒ Hash

Returns decoded JSON body (empty Hash for 204/no-content).

Parameters:

  • method (Symbol)

    :get, :post, :patch, :put, :delete

  • path (String)

    e.g. "/articles"

  • data (Hash) (defaults to: {})

    request body (post/put/patch only)

Returns:

  • (Hash)

    decoded JSON body (empty Hash for 204/no-content)

Raises:



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/misarblog/client.rb', line 270

def request(method, path, data = {})
  url       = URI.parse(@base_url + "/" + path.delete_prefix("/"))
  has_body  = !data.empty? && %i[post put patch].include?(method)
  json_body = has_body ? JSON.generate(data) : nil

  last_status = 0

  @max_retries.times do |attempt|
    final = attempt == @max_retries - 1
    begin
      http              = Net::HTTP.new(url.host, url.port)
      http.use_ssl      = url.scheme == "https"
      http.open_timeout = 10
      http.read_timeout = @timeout

      req  = build_request(method, url, json_body)
      resp = http.request(req)
      last_status = resp.code.to_i

      # Retry on transient statuses — but on the FINAL attempt, still
      # return/raise from the real response (never swallow the send).
      # A plan-limit 429 is not "slow down" — retrying cannot help until
      # the allowance resets or the plan changes, so fall straight through
      # to parse_response and raise instead of burning the retry budget.
      if RETRYABLE.include?(last_status) && !final && !plan_limit?(resp)
        sleep(RETRY_BASE_S * (2**attempt))
        next
      end

      return parse_response(resp, last_status)
    rescue Net::OpenTimeout, Net::ReadTimeout,
           Errno::ECONNREFUSED, Errno::ECONNRESET, SocketError => e
      raise NetworkError.new(e.message, e) if final

      sleep(RETRY_BASE_S * (2**attempt))
      next
    end
  end

  raise ApiError.new(last_status, "Max retries exceeded")
end