Compare commits

..

No commits in common. "abfe1e6df7dcc3b84752dff2cec8a79eb81bf43a" and "23c083ff1d0056b3c56e42795823db468039c615" have entirely different histories.

10 changed files with 79 additions and 85 deletions

View file

@ -161,7 +161,7 @@ class AuthUser < AuthRecord
# means we can wrap it in a `with_timeout` block!)
neopets_username = Sync do |task|
task.with_timeout(5) do
Neopets::NeoPass.load_main_neopets_username(auth.credentials.token)
NeoPass.load_main_neopets_username(auth.credentials.token)
end
rescue Async::TimeoutError
nil # If the request times out, just move on!

View file

@ -1,4 +1,13 @@
require 'rocketamf_extensions/remote_gateway'
require 'ostruct'
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
@ -8,7 +17,7 @@ class Pet < ApplicationRecord
}
def load!(timeout: nil)
viewer_data = Neopets::CustomPets.fetch_viewer_data(name, timeout:)
viewer_data = self.class.fetch_viewer_data(name, timeout:)
use_viewer_data(viewer_data)
end
@ -27,7 +36,7 @@ class Pet < ApplicationRecord
)
begin
new_image_hash = Neopets::CustomPets.fetch_image_hash(self.name)
new_image_hash = Pet.fetch_image_hash(self.name)
rescue => error
Rails.logger.warn "Failed to load image hash: #{error.full_message}"
end
@ -114,5 +123,61 @@ 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

View file

@ -1,7 +1,7 @@
require "addressable/template"
require "async/http/internet/instance"
module Neopets::NCMall
module NCMall
# Share a pool of persistent connections, rather than reconnecting on
# each request. (This library does that automatically!)
INTERNET = Async::HTTP::Internet.instance

View file

@ -2,7 +2,7 @@ require "async/http/internet/instance"
# While most of our NeoPass logic is built into Devise -> OmniAuth -> OIDC
# OmniAuth plugin, NeoPass also offers some supplemental APIs that we use here.
module Neopets::NeoPass
module NeoPass
# Share a pool of persistent connections, rather than reconnecting on
# each request. (This library does that automatically!)
INTERNET = Async::HTTP::Internet.instance

View file

@ -1,64 +0,0 @@
require 'rocketamf_extensions/remote_gateway'
module Neopets::CustomPets
GATEWAY_URL =
Addressable::URI.parse(Rails.configuration.neopets_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

View file

@ -7,9 +7,8 @@
:markdown
Pet Styles drastically change the appearance of your pet! They're [available
in the NC Mall][1], or via "NC Trading". Some of them are "Nostalgic",
meaning they're reminiscent of classic Neopets designs from long ago—and some
are brand new!
in the NC Mall][1], or via "NC Trading". They're generally reminiscent of
classic Neopets designs from long ago.
Pet Styles only fit pets of the same species—but the *color* of the pet
doesn't matter! A Blue Acara can wear the "Nostalgic Faerie Acara" Pet Style.

View file

@ -72,12 +72,6 @@ module OpenneoImpressItems
# version number, etc. So let's only send this to Neopets systems, where it
# should hopefully be clear who we are from context!
config.user_agent_for_neopets = "Dress to Impress"
# Use the usual Neopets.com, unless we have an override. (At times, we've
# used this in collaboration with TNT to address the server directly,
# instead of through the CDN.)
config.neopets_origin =
ENV.fetch('NEOPETS_URL_ORIGIN', 'https://www.neopets.com')
end
end

View file

@ -19,10 +19,10 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
# Teach Zeitwerk that `RocketAMF` is what to expect in `lib/rocketamf`.
inflect.acronym "RocketAMF"
# Teach Zeitwerk that `NeoPass` is what to expect in `neopass.rb`.
# Teach Zeitwerk that `NeoPass` is what to expect in `app/services/neopass.rb`.
inflect.acronym "NeoPass"
# Teach Zeitwerk that "NCMall" is what to expect in `nc_mall.rb`.
# Teach Zeitwerk that "NCMall" is what to expect in `app/services/nc_mall.rb`.
# (We do this by teaching it the word "NC".)
inflect.acronym "NC"
end

View file

@ -77,17 +77,17 @@ end
def load_all_nc_mall_pages
Sync do
# First, start loading the homepage.
homepage_task = Async { Neopets::NCMall.load_home_page }
homepage_task = Async { NCMall.load_home_page }
# Next, load the page links for different categories etc.
links = Neopets::NCMall.load_page_links
links = NCMall.load_page_links
# Next, load the linked pages, 10 at a time.
barrier = Async::Barrier.new
semaphore = Async::Semaphore.new(10, parent: barrier)
begin
linked_page_tasks = links.map do |link|
semaphore.async { Neopets::NCMall.load_page link[:type], link[:cat] }
semaphore.async { NCMall.load_page link[:type], link[:cat] }
end
barrier.wait # Load all the pages.
ensure

View file

@ -1,7 +1,7 @@
namespace :pets do
desc "Load a pet's viewer data"
task :load, [:name] => [:environment] do |task, args|
pp Neopets::CustomPets.fetch_viewer_data(args[:name])
pp Pet.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)