diff --git a/app/models/pet.rb b/app/models/pet.rb index 756e9af3..d0d6ceed 100644 --- a/app/models/pet.rb +++ b/app/models/pet.rb @@ -1,12 +1,4 @@ -require 'rocketamf_extensions/remote_gateway' - class Pet < ApplicationRecord - NEOPETS_URL_ORIGIN = ENV['NEOPETS_URL_ORIGIN'] || 'https://www.neopets.com' - GATEWAY_URL = NEOPETS_URL_ORIGIN + '/amfphp/gateway.php' - GATEWAY = RocketAMFExtensions::RemoteGateway.new(GATEWAY_URL) - CUSTOM_PET_SERVICE = GATEWAY.service('CustomPetService') - PET_SERVICE = GATEWAY.service('PetService') - belongs_to :pet_type attr_reader :items, :pet_state, :alt_style @@ -16,7 +8,7 @@ class Pet < ApplicationRecord } def load!(timeout: nil) - viewer_data = self.class.fetch_viewer_data(name, timeout:) + viewer_data = Neopets::CustomPets.fetch_viewer_data(name, timeout:) use_viewer_data(viewer_data) end @@ -35,7 +27,7 @@ class Pet < ApplicationRecord ) begin - new_image_hash = Pet.fetch_image_hash(self.name) + new_image_hash = Neopets::CustomPets.fetch_image_hash(self.name) rescue => error Rails.logger.warn "Failed to load image hash: #{error.full_message}" end @@ -122,61 +114,5 @@ class Pet < ApplicationRecord pet.load!(**options) pet end - - # NOTE: Ideally pet requests shouldn't take this long, but Neopets can be - # 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. - def self.fetch_viewer_data(name, timeout: 10) - request = CUSTOM_PET_SERVICE.action('getViewerData').request([name]) - send_amfphp_request(request).tap do |data| - if data[:custom_pet][:name].blank? - raise PetNotFound, "Pet #{name.inspect} does not exist" - end - end - end - - def self.fetch_metadata(name, timeout: 10) - # If this is an image hash "pet name", it has no metadata. - return nil if name.start_with?("@") - - 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` - # image URLs. (This corresponds to its current biology and items.) - def self.fetch_image_hash(name, timeout: 10) - # If this is an image hash "pet name", just take off the `@`! - return name[1..] if name.start_with?("@") - - metadata = fetch_metadata(name, timeout:) - metadata[:hash] - end - - class PetNotFound < RuntimeError;end - class DownloadError < 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_data = request.post(timeout: timeout, headers: { - "User-Agent" => Rails.configuration.user_agent_for_neopets, - }) - rescue RocketAMFExtensions::RemoteGateway::AMFError => e - raise DownloadError, e.message - rescue RocketAMFExtensions::RemoteGateway::ConnectionError => e - raise DownloadError, e.message, e.backtrace - end - - HashWithIndifferentAccess.new(response_data) - end end diff --git a/app/services/neopets/custom_pets.rb b/app/services/neopets/custom_pets.rb new file mode 100644 index 00000000..98548de1 --- /dev/null +++ b/app/services/neopets/custom_pets.rb @@ -0,0 +1,63 @@ +require 'rocketamf_extensions/remote_gateway' + +module Neopets::CustomPets + NEOPETS_URL_ORIGIN = ENV['NEOPETS_URL_ORIGIN'] || 'https://www.neopets.com' + GATEWAY_URL = NEOPETS_URL_ORIGIN + '/amfphp/gateway.php' + GATEWAY = RocketAMFExtensions::RemoteGateway.new(GATEWAY_URL) + CUSTOM_PET_SERVICE = GATEWAY.service('CustomPetService') + PET_SERVICE = GATEWAY.service('PetService') + + class << self + # NOTE: Ideally pet requests shouldn't take this long, but Neopets can be + # 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. + def fetch_viewer_data(name, timeout: 10) + request = CUSTOM_PET_SERVICE.action('getViewerData').request([name]) + send_amfphp_request(request).tap do |data| + if data[:custom_pet][:name].blank? + raise PetNotFound, "Pet #{name.inspect} does not exist" + end + end + end + + def fetch_metadata(name, timeout: 10) + # If this is an image hash "pet name", it has no metadata. + return nil if name.start_with?("@") + + 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` + # image URLs. (This corresponds to its current biology and items.) + def fetch_image_hash(name, timeout: 10) + # If this is an image hash "pet name", just take off the `@`! + return name[1..] if name.start_with?("@") + + metadata = fetch_metadata(name, timeout:) + metadata[:hash] + end + + private + + # Send an AMFPHP request, re-raising errors as `Pet::DownloadError`. + # Return the response body as a `HashWithIndifferentAccess`. + def send_amfphp_request(request, timeout: 10) + begin + response_data = request.post(timeout: timeout, headers: { + "User-Agent" => Rails.configuration.user_agent_for_neopets, + }) + rescue RocketAMFExtensions::RemoteGateway::AMFError => e + raise DownloadError, e.message + rescue RocketAMFExtensions::RemoteGateway::ConnectionError => e + raise DownloadError, e.message, e.backtrace + end + + HashWithIndifferentAccess.new(response_data) + end + end +end diff --git a/lib/tasks/pets.rake b/lib/tasks/pets.rake index 03ab4319..6ac57533 100644 --- a/lib/tasks/pets.rake +++ b/lib/tasks/pets.rake @@ -1,7 +1,7 @@ namespace :pets do desc "Load a pet's viewer data" task :load, [:name] => [:environment] do |task, args| - pp Pet.fetch_viewer_data(args[:name]) + pp Neopets::CustomPets.fetch_viewer_data(args[:name]) end desc "Find pets that were, last we saw, of the given color and species" @@ -10,7 +10,7 @@ namespace :pets do pt = PetType.matching_name(args.color_name, args.species_name).first! rescue ActiveRecord::RecordNotFound abort "Could not find pet type for " + - "#{args.color_name} #{args.species_name}" + "#{args.color_name} #{args.species_name}" end limit = ENV.fetch("LIMIT", 10)