Refactor image hash loading to use PetService.getPet
, not CPN redirs
Previously, the way we loaded the image hash for a given pet was to navigate to `https://pets.neopets.com/cpn/<pet_name>/1/1.png`, but *not* follow the redirect, and extract the image hash from the URL where it redirected us to. In this change, we refactor to use the AMFPHP RPC `PetService.getPet` instead. I don't think it had this data last time I looked at it, but now it does! Much prefer to use an actual RPC than our weird hacky thing! (We might also be able to use this call for other stuff, like auto-labeling gender & mood for pet states, maybe?? That's in this data too! We used to load petlookups for this, long long ago, before the petlookup captchas got added.)
This commit is contained in:
parent
1d3aac436b
commit
bd4b67316c
1 changed files with 33 additions and 50 deletions
|
@ -4,8 +4,9 @@ require 'ostruct'
|
||||||
class Pet < ApplicationRecord
|
class Pet < ApplicationRecord
|
||||||
NEOPETS_URL_ORIGIN = ENV['NEOPETS_URL_ORIGIN'] || 'https://www.neopets.com'
|
NEOPETS_URL_ORIGIN = ENV['NEOPETS_URL_ORIGIN'] || 'https://www.neopets.com'
|
||||||
GATEWAY_URL = NEOPETS_URL_ORIGIN + '/amfphp/gateway.php'
|
GATEWAY_URL = NEOPETS_URL_ORIGIN + '/amfphp/gateway.php'
|
||||||
PET_VIEWER = RocketAMFExtensions::RemoteGateway.new(GATEWAY_URL).
|
GATEWAY = RocketAMFExtensions::RemoteGateway.new(GATEWAY_URL)
|
||||||
service('CustomPetService').action('getViewerData')
|
CUSTOM_PET_SERVICE = GATEWAY.service('CustomPetService')
|
||||||
|
PET_SERVICE = GATEWAY.service('PetService')
|
||||||
|
|
||||||
belongs_to :pet_type
|
belongs_to :pet_type
|
||||||
|
|
||||||
|
@ -123,66 +124,48 @@ class Pet < ApplicationRecord
|
||||||
# slow sometimes! Since we're on the Falcon server, long timeouts shouldn't
|
# slow sometimes! Since we're on the Falcon server, long timeouts shouldn't
|
||||||
# slow down the rest of the request queue, like it used to be in the past.
|
# slow down the rest of the request queue, like it used to be in the past.
|
||||||
def self.fetch_viewer_data(name, timeout: 10)
|
def self.fetch_viewer_data(name, timeout: 10)
|
||||||
begin
|
request = CUSTOM_PET_SERVICE.action('getViewerData').request([name])
|
||||||
envelope = PET_VIEWER.request([name, 0]).post(timeout: timeout)
|
send_amfphp_request(request).tap do |data|
|
||||||
rescue RocketAMFExtensions::RemoteGateway::AMFError => e
|
|
||||||
raise DownloadError, e.message
|
|
||||||
rescue RocketAMFExtensions::RemoteGateway::ConnectionError => e
|
|
||||||
raise DownloadError, e.message, e.backtrace
|
|
||||||
end
|
|
||||||
|
|
||||||
HashWithIndifferentAccess.new(envelope.messages[0].data.body).tap do |data|
|
|
||||||
# When the pet doesn't exist, the service returns an empty pet object.
|
|
||||||
if data[:custom_pet][:name].blank?
|
if data[:custom_pet][:name].blank?
|
||||||
raise PetNotFound, "Pet #{name.inspect} does not exist"
|
raise PetNotFound, "Pet #{name.inspect} does not exist"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.fetch_metadata(name, timeout: 10)
|
||||||
|
request = PET_SERVICE.action('getPet').request([name])
|
||||||
|
send_amfphp_request(request).tap do |data|
|
||||||
|
if data[:name].blank?
|
||||||
|
raise PetNotFound, "Pet #{name.inspect} does not exist"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Given a pet's name, load its image hash, for use in `pets.neopets.com`
|
# Given a pet's name, load its image hash, for use in `pets.neopets.com`
|
||||||
# image URLs. (This corresponds to its current biology and items.)
|
# image URLs. (This corresponds to its current biology and items.)
|
||||||
IMAGE_CPN_FORMAT = 'https://pets.neopets.com/cpn/%s/1/1.png';
|
def self.fetch_image_hash(name, timeout: 10)
|
||||||
IMAGE_CP_LOCATION_REGEX = %r{^/cp/(.+?)/[0-9]+/[0-9]+\.png$};
|
metadata = fetch_metadata(name, timeout:)
|
||||||
IMAGE_CPN_ACCEPTABLE_NAME = /^[A-Za-z0-9_]+$/
|
metadata[:hash]
|
||||||
def self.fetch_image_hash(name)
|
|
||||||
return nil unless name.match?(IMAGE_CPN_ACCEPTABLE_NAME)
|
|
||||||
|
|
||||||
cpn_uri = URI.parse sprintf(IMAGE_CPN_FORMAT, CGI.escape(name))
|
|
||||||
begin
|
|
||||||
res = Net::HTTP.get_response(cpn_uri, {
|
|
||||||
'User-Agent' => Rails.configuration.user_agent_for_neopets
|
|
||||||
})
|
|
||||||
rescue Exception => e
|
|
||||||
raise DownloadError, e.message
|
|
||||||
end
|
|
||||||
unless res.is_a? Net::HTTPFound
|
|
||||||
begin
|
|
||||||
res.error!
|
|
||||||
rescue Exception => e
|
|
||||||
Rails.logger.warn "Error loading CPN image at #{cpn_uri}: #{e.message}"
|
|
||||||
return
|
|
||||||
else
|
|
||||||
Rails.logger.warn "Error loading CPN image at #{cpn_uri}. Response: #{res.inspect}"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
new_url = res['location']
|
|
||||||
new_image_hash = get_hash_from_cp_path(new_url)
|
|
||||||
if new_image_hash
|
|
||||||
Rails.logger.info "Successfully loaded #{cpn_uri}, with image hash #{new_image_hash}"
|
|
||||||
new_image_hash
|
|
||||||
else
|
|
||||||
Rails.logger.warn "CPN image pointed to #{new_url}, which does not match CP image format"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.get_hash_from_cp_path(path)
|
|
||||||
match = path.match(IMAGE_CP_LOCATION_REGEX)
|
|
||||||
match ? match[1] : nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class PetNotFound < RuntimeError;end
|
class PetNotFound < RuntimeError;end
|
||||||
class DownloadError < RuntimeError;end
|
class DownloadError < RuntimeError;end
|
||||||
class UnexpectedDataFormat < RuntimeError;end
|
class UnexpectedDataFormat < RuntimeError;end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Send an AMFPHP request, re-raising errors as `Pet::DownloadError`.
|
||||||
|
# Return the response body as a `HashWithIndifferentAccess`.
|
||||||
|
def self.send_amfphp_request(request, timeout: 10)
|
||||||
|
begin
|
||||||
|
response = request.post(timeout: timeout)
|
||||||
|
rescue RocketAMFExtensions::RemoteGateway::AMFError => e
|
||||||
|
raise DownloadError, e.message
|
||||||
|
rescue RocketAMFExtensions::RemoteGateway::ConnectionError => e
|
||||||
|
raise DownloadError, e.message, e.backtrace
|
||||||
|
end
|
||||||
|
|
||||||
|
HashWithIndifferentAccess.new(response.messages[0].data.body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue