schmich/kappa

View on GitHub
lib/kappa/connection.rb

Summary

Maintainability
B
6 hrs
Test Coverage
require 'httparty'
require 'addressable/uri'
require 'json'
require 'set'

module Twitch
  # @private
  class Connection
    include HTTParty

    def initialize(client_id, base_url = DEFAULT_BASE_URL)
      raise ArgumentError, 'client_id' if !client_id || client_id.empty?
      raise ArgumentError, 'base_url' if !base_url || base_url.empty?

      @client_id = client_id
      @base_url = Addressable::URI.parse(base_url)
    end

    def get(path, query = nil)
      raise ArgumentError, 'path' if !path || path.empty?

      request_url = @base_url + path

      all_headers = {
        'Client-ID' => @client_id,
        'Kappa-Version' => Twitch::VERSION
      }.merge(headers())

      response = self.class.get(request_url, :headers => all_headers, :query => query)

      url = response.request.last_uri.to_s
      status = response.code
      body = response.body

      case status
        when 400...500
          raise Error::ClientError.new("HTTP client error, status #{status}.", url, status, body)
        when 500...600
          raise Error::ServerError.new("HTTP server error, status #{status}.", url, status, body)
        else
          # Ignore, assume success.
      end

      begin
        return JSON.parse(body)
      rescue JSON::ParserError => e
        raise Error::FormatError.new(e, url, status, body)
      end
    end

    def accumulate(options, &block)
      path = options[:path]
      params = options[:params] || {}
      json = options[:json]
      sub_json = options[:sub_json]
      create = options[:create]

      raise ArgumentError, 'json' if json.nil?
      raise ArgumentError, 'path' if path.nil?
      raise ArgumentError, 'create' if create.nil?

      if create.is_a? Class
        klass = create
        create = -> hash { klass.new(hash) }
      end

      total_limit = options[:limit]
      page_limit = [total_limit || 100, 100].min
      offset = options[:offset] || 0

      ids = Set.new
      objects = []
      count = 0

      block ||= -> object {
        objects << object
      }

      paginate(path, page_limit, offset, params) do |response_json|
        current_objects = response_json[json]
        current_objects.each do |object_json|
          object_json = object_json[sub_json] if sub_json
          object = create.call(object_json)
          if ids.add?(object.id)
            count += 1
            block.call(object)
            if count == total_limit
              return block_given? ? nil : objects
            end
          end
        end

        break if current_objects.empty? || (current_objects.count < page_limit)
      end

      return block_given? ? nil : objects
    end

    def paginate(path, limit, offset, params = {})
      path_uri = Addressable::URI.parse(path)
      query = { 'limit' => limit, 'offset' => offset }
      path_uri.query_values ||= {}
      path_uri.query_values = path_uri.query_values.merge(query)

      request_url = path_uri.to_s

      json = get(request_url, params)

      loop do
        break if json['error'] && (json['status'] == 503)
        yield json

        links = json['_links']
        next_url = links['next']

        next_uri = Addressable::URI.parse(next_url)
        offset = next_uri.query_values['offset'].to_i

        total = json['_total']
        break if total && (offset > total)

        request_url = next_url
        json = get(request_url, params)
      end
    end

  private
    DEFAULT_BASE_URL = 'https://api.twitch.tv/kraken/'
  end
end

module Twitch::V2
  # @private
  class Connection < Twitch::Connection
    def headers
      { 'Accept' => 'application/vnd.twitchtv.v2+json' }
    end
  end
end