class ApkDownloader::Api

Constants

GoogleApiUri
LoginUri

Attributes

auth_token[R]

Public Class Methods

new() click to toggle source
# File lib/apk_downloader/api.rb, line 12
def initialize
  @details_messages = {}
end

Public Instance Methods

details(package) click to toggle source
# File lib/apk_downloader/api.rb, line 60
def details package
  if @details_messages[package].nil?
    log_in!
    message = api_request :get, '/details', :doc => package
    @details_messages[package] = message.payload
  end

  return @details_messages[package]
end
fetch_apk_data(package) click to toggle source
# File lib/apk_downloader/api.rb, line 70
def fetch_apk_data package
  log_in!
  doc = details(package).detailsResponse.docV2
  version_code = doc.details.appDetails.versionCode
  offer_type = doc.offer[0].offerType

  message = api_request :post, '/purchase', :ot => offer_type, :doc => package, :vc => version_code

  url = URI(message.payload.buyResponse.purchaseStatusResponse.appDeliveryData.downloadUrl)
  cookie = message.payload.buyResponse.purchaseStatusResponse.appDeliveryData.downloadAuthCookie[0]

  resp = recursive_apk_fetch(url, cookie)

  return resp.body
end
log_in!() click to toggle source
# File lib/apk_downloader/api.rb, line 20
def log_in!
  return if self.logged_in?

  params = {
    "Email" => ApkDownloader.configuration.email,
    "Passwd" => ApkDownloader.configuration.password,
    "service" => "androidmarket",
    "accountType" => "HOSTED_OR_GOOGLE",
    "has_permission" => "1",
    "source" => "android",
    "androidId" => ApkDownloader.configuration.android_id,
    "app" => "com.android.vending",
    "device_country" => "fr",
    "operatorCountry" => "fr",
    "lang" => "fr",
    "sdk_version" => "16"
  }

  login_http = Net::HTTP.new LoginUri.host, LoginUri.port
  login_http.use_ssl = true
  login_http.verify_mode  = OpenSSL::SSL::VERIFY_NONE

  post = Net::HTTP::Post.new LoginUri.to_s
  post.set_form_data params
  post["Accept-Encoding"] = ""

  response = login_http.request post

  if ApkDownloader.configuration.debug
    pp "Login response:"
    pp response
  end

  if response.body =~ /error/i
    raise "Unable to authenticate with Google"
  elsif response.body.include? "Auth="
    @auth_token = response.body.scan(/Auth=(.*?)$/).flatten.first
  end
end
logged_in?() click to toggle source
# File lib/apk_downloader/api.rb, line 16
def logged_in?
  !self.auth_token.nil?
end

Private Instance Methods

api_request(type, path, data = {}) click to toggle source
# File lib/apk_downloader/api.rb, line 111
def api_request type, path, data = {}
  if @http.nil?
    @http = Net::HTTP.new GoogleApiUri.host, GoogleApiUri.port
    @http.use_ssl = true
    @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  api_headers = {
    "Accept-Language" => "en_US",
    "Authorization" => "GoogleLogin auth=#{@auth_token}",
    "X-DFE-Enabled-Experiments" => "cl:billing.select_add_instrument_by_default",
    "X-DFE-Unsupported-Experiments" => "nocache:billing.use_charging_poller,market_emails,buyer_currency,prod_baseline,checkin.set_asset_paid_app_field,shekel_test,content_ratings,buyer_currency_in_app,nocache:encrypted_apk,recent_changes",
    "X-DFE-Device-Id" => ApkDownloader.configuration.android_id,
    "X-DFE-Client-Id" => "am-android-google",
    "User-Agent" => "Android-Finsky/3.7.13 (api=3,versionCode=8013013,sdk=16,device=crespo,hardware=herring,product=soju)",
    "X-DFE-SmallestScreenWidthDp" => "320",
    "X-DFE-Filter-Level" => "3",
    "Accept-Encoding" => "",
    "Host" => "android.clients.google.com"
  }

  if type == :post
    api_headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
  end

  uri = URI([GoogleApiUri,path.sub(/^\//,'')].join('/'))

  req = if type == :get
    uri.query = URI.encode_www_form data
    Net::HTTP::Get.new uri.to_s
  else
    post = Net::HTTP::Post.new uri.to_s
    post.tap { |p| p.set_form_data data }
  end

  api_headers.each { |k, v| req[k] = v }

  resp = @http.request req

  unless resp.code.to_i == 200 or resp.code.to_i == 302
    raise "Bad status (#{resp.code}) from Play API (#{path}) => #{data}"
  end

  if ApkDownloader.configuration.debug
    pp "Request response (#{type}):"
    pp resp
  end

  return ApkDownloader::ProtocolBuffers::ResponseWrapper.new.parse(resp.body)
end
recursive_apk_fetch(url, cookie, tries = 5) click to toggle source
# File lib/apk_downloader/api.rb, line 87
def recursive_apk_fetch url, cookie, tries = 5
  raise ArgumentError, 'HTTP redirect too deep' if tries == 0

  http = Net::HTTP.new url.host, url.port
  http.use_ssl = (url.scheme == 'https')
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  req = Net::HTTP::Get.new url.to_s
  req['Accept-Encoding'] = ''
  req['User-Agent'] = 'AndroidDownloadManager/4.1.1 (Linux; U; Android 4.1.1; Nexus S Build/JRO03E)'
  req['Cookie'] = [cookie.name, cookie.value].join('=')

  resp = http.request req

  case resp
  when Net::HTTPSuccess
    return resp
  when Net::HTTPRedirection
    return recursive_apk_fetch(URI(resp['Location']), cookie, tries - 1)
  else
    resp.error!
  end
end