fic_tracker/lib/fic_tracker/backends/ao3/client.rb

100 lines
2.5 KiB
Ruby

# frozen_string_literal: true
require 'cgi'
require 'net/http'
require 'nokogiri'
require 'json'
module FicTracker::Backends::Ao3
class Client
BASE_URL = 'https://archiveofourown.org'
def url
URI(BASE_URL)
end
def request(path, type: :html, body: nil, query: nil, method: :get, redirect: true)
uri = URI.join(url, path)
uri.query = URI.encode_www_form(query) if query
req = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new uri.request_uri
req['User-Agent'] = "FicTracker/#{FicTracker::VERSION}"
case type
when :html, :xml
req['Accept'] = 'text/html,application/xhtml+xml,application/xml'
when :json
req['Accept'] = 'application/json'
end
if body
req.body = body
req.body = req.body.to_json unless req.body.is_a? String
req['Content-Type'] = 'application/json'
end
resp = nil
http.start do
loop do
debug_http(req)
resp = http.request req
debug_http(resp)
case resp
when Net::HTTPRedirection
req.path.replace resp['location']
when Net::HTTPTooManyRequests
wait_time = 10
if resp['retry-after']
after = resp['retry-after']
wait_time = after =~ /\A[0-9]+\Z/ ? after.to_i : Time.parse(after)
end
wait_time = Time.now + wait_time if wait_time.is_a? Numeric
logger.info "Rate limited, waiting until #{wait_time} before retrying"
sleep Time.now - wait_time
else
break
end
end
end
resp.value
return if resp.body.empty?
case type
when :html
Nokogiri::HTML4.parse(resp.body)
when :xml
Nokogiri::XML.parse(resp.body)
when :json
JSON.parse(resp.body, symbolize_names: true)
when :raw
resp.body
end
end
private
def logger
Logging.logger[self]
end
def http
@http ||= Net::HTTP.new(url.host, url.port).tap { |http| http.use_ssl = url.scheme == 'https' }
end
def debug_http(object)
return unless logger.debug?
dir = '>'
if object.is_a?(Net::HTTPRequest)
dir = '<'
logger.debug "#{dir} #{object.method} #{object.path}"
else
logger.debug "#{dir} #{object.code} #{object.message}"
end
object.each_header { |h, v| logger.debug "#{dir} #{h}: #{v}" }
end
end
end