2024-01-24 00:59:11 -08:00
|
|
|
require 'rocketamf_extensions/remote_gateway'
|
2013-01-28 13:18:11 -08:00
|
|
|
require 'ostruct'
|
2011-05-31 07:36:32 -07:00
|
|
|
|
2023-08-02 16:05:02 -07:00
|
|
|
class Pet < ApplicationRecord
|
2023-10-12 18:14:20 -07:00
|
|
|
NEOPETS_URL_ORIGIN = ENV['NEOPETS_URL_ORIGIN'] || 'https://www.neopets.com'
|
Use secret NEOPETS_URL_ORIGIN to bypass HTTPS
Oops, neopets.com finally stopped accepting `http://` connections, so our AMFPHP requests stopped working! And our current dependencies make it hard to make modern HTTPS requests :(
Instead, we're doing this quick-fix: we have a connection who knows the internal address for the Neopets origin server behind their CDN, which *does* still accept `http://` requests!
So, when `NEOPETS_URL_ORIGIN` is specified in the secret `.env` file (not committed to the repository), we'll use it instead of `http://www.neopets.com`. However, we still have that in the code as a fallback, just to be a bit less surprising to some theoretical future dev so they can see the real error message, and to self-document a bit of what that value is semantically doing! (The documentation angle is more of why it's there, rather than an actual expectation that any actual person in the future will run the code and get the fallback.)
2022-08-02 20:46:47 -07:00
|
|
|
GATEWAY_URL = NEOPETS_URL_ORIGIN + '/amfphp/gateway.php'
|
2024-01-24 00:59:11 -08:00
|
|
|
PET_VIEWER = RocketAMFExtensions::RemoteGateway.new(GATEWAY_URL).
|
2013-01-11 17:16:16 -08:00
|
|
|
service('CustomPetService').action('getViewerData')
|
2010-10-07 07:46:23 -07:00
|
|
|
PET_NOT_FOUND_REMOTE_ERROR = 'PHP: Unable to retrieve records from the database.'
|
2010-10-10 11:33:54 -07:00
|
|
|
WARDROBE_PATH = '/wardrobe'
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2010-10-07 07:46:23 -07:00
|
|
|
belongs_to :pet_type
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2024-01-24 03:25:23 -08:00
|
|
|
attr_reader :items, :pet_state, :alt_style
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2023-07-22 14:04:01 -07:00
|
|
|
scope :with_pet_type_color_ids, ->(color_ids) {
|
2010-11-27 15:41:06 -08:00
|
|
|
joins(:pet_type).where(PetType.arel_table[:id].in(color_ids))
|
|
|
|
}
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2013-01-13 18:10:01 -08:00
|
|
|
def load!(options={})
|
|
|
|
options[:locale] ||= I18n.default_locale
|
2013-12-08 20:59:36 -08:00
|
|
|
I18n.with_locale(options.delete(:locale)) do
|
2024-01-24 00:51:20 -08:00
|
|
|
use_viewer_data(
|
|
|
|
self.class.fetch_viewer_data(name, options.delete(:timeout)),
|
|
|
|
options,
|
|
|
|
)
|
2013-01-13 18:10:01 -08:00
|
|
|
end
|
2010-10-07 07:46:23 -07:00
|
|
|
true
|
|
|
|
end
|
2013-12-08 20:59:36 -08:00
|
|
|
|
|
|
|
def use_viewer_data(viewer_data, options={})
|
2023-11-09 21:35:42 -08:00
|
|
|
options[:item_scope] ||= Item.all
|
2013-12-08 20:59:36 -08:00
|
|
|
|
|
|
|
pet_data = viewer_data[:custom_pet]
|
|
|
|
|
2024-01-24 03:25:23 -08:00
|
|
|
raise UnexpectedDataFormat unless pet_data[:species_id]
|
|
|
|
raise UnexpectedDataFormat unless pet_data[:color_id]
|
|
|
|
raise UnexpectedDataFormat unless pet_data[:body_id]
|
2024-01-24 00:54:30 -08:00
|
|
|
|
2023-10-12 18:05:01 -07:00
|
|
|
self.pet_type = PetType.find_or_initialize_by(
|
|
|
|
species_id: pet_data[:species_id].to_i,
|
|
|
|
color_id: pet_data[:color_id].to_i
|
2013-12-08 20:59:36 -08:00
|
|
|
)
|
|
|
|
self.pet_type.body_id = pet_data[:body_id]
|
|
|
|
self.pet_type.origin_pet = self
|
2024-01-24 03:25:23 -08:00
|
|
|
|
|
|
|
pet_state_biology = pet_data[:alt_style] ?
|
|
|
|
pet_data[:original_biology] : pet_data[:biology_by_zone]
|
|
|
|
raise UnexpectedDataFormat if pet_state_biology.empty?
|
|
|
|
pet_state_biology[0] = nil # remove effects if present
|
|
|
|
@pet_state = self.pet_type.add_pet_state_from_biology! pet_state_biology
|
|
|
|
|
|
|
|
if pet_data[:alt_style]
|
|
|
|
raise UnexpectedDataFormat unless pet_data[:alt_color]
|
|
|
|
raise UnexpectedDataFormat if pet_data[:biology_by_zone].empty?
|
|
|
|
|
2024-01-24 04:01:34 -08:00
|
|
|
@alt_style = AltStyle.find_or_initialize_by(id: pet_data[:alt_style].to_i)
|
|
|
|
@alt_style.assign_attributes(
|
2024-01-24 03:25:23 -08:00
|
|
|
color_id: pet_data[:alt_color].to_i,
|
|
|
|
species_id: pet_data[:species_id].to_i,
|
|
|
|
body_id: pet_data[:body_id].to_i,
|
|
|
|
biology: pet_data[:biology_by_zone],
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2013-12-08 20:59:36 -08:00
|
|
|
@items = Item.collection_from_pet_type_and_registries(self.pet_type,
|
|
|
|
viewer_data[:object_info_registry], viewer_data[:object_asset_registry],
|
|
|
|
options[:item_scope])
|
|
|
|
end
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2010-10-10 19:18:42 -07:00
|
|
|
def wardrobe_query
|
|
|
|
{
|
|
|
|
:name => self.name,
|
|
|
|
:color => self.pet_type.color.id,
|
|
|
|
:species => self.pet_type.species.id,
|
|
|
|
:state => self.pet_state.id,
|
|
|
|
:objects => self.items.map(&:id)
|
|
|
|
}.to_query
|
2010-10-10 11:33:54 -07:00
|
|
|
end
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2010-11-06 16:07:15 -07:00
|
|
|
def contributables
|
2024-01-24 03:54:43 -08:00
|
|
|
contributables = [pet_type, @pet_state, @alt_style].filter(&:present?)
|
2010-11-06 16:07:15 -07:00
|
|
|
items.each do |item|
|
|
|
|
contributables << item
|
|
|
|
contributables += item.pending_swf_assets
|
|
|
|
end
|
|
|
|
contributables
|
|
|
|
end
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2010-11-06 15:08:42 -07:00
|
|
|
before_validation do
|
2010-11-06 16:07:15 -07:00
|
|
|
pet_type.save!
|
2011-10-31 14:22:24 -07:00
|
|
|
if @pet_state
|
|
|
|
@pet_state.save!
|
2012-01-13 13:56:31 -08:00
|
|
|
@pet_state.handle_assets!
|
2011-10-31 14:22:24 -07:00
|
|
|
end
|
|
|
|
|
2011-05-21 19:32:01 -07:00
|
|
|
if @items
|
|
|
|
@items.each do |item|
|
2013-01-28 00:10:25 -08:00
|
|
|
item.save! if item.changed?
|
2012-01-13 13:56:31 -08:00
|
|
|
item.handle_assets!
|
2011-05-21 19:32:01 -07:00
|
|
|
end
|
2010-11-06 15:08:42 -07:00
|
|
|
end
|
2024-01-24 03:25:23 -08:00
|
|
|
|
|
|
|
if @alt_style
|
|
|
|
@alt_style.save!
|
|
|
|
end
|
2010-11-06 15:08:42 -07:00
|
|
|
end
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2013-01-13 18:10:01 -08:00
|
|
|
def self.load(name, options={})
|
2023-10-12 18:05:01 -07:00
|
|
|
pet = Pet.find_or_initialize_by(name: name)
|
2013-01-13 18:10:01 -08:00
|
|
|
pet.load!(options)
|
2010-10-07 07:46:23 -07:00
|
|
|
pet
|
|
|
|
end
|
2011-05-21 19:32:01 -07:00
|
|
|
|
2013-12-08 20:59:36 -08:00
|
|
|
def self.from_viewer_data(viewer_data, options={})
|
2023-10-12 18:05:01 -07:00
|
|
|
pet = Pet.find_or_initialize_by(name: viewer_data[:custom_pet][:name])
|
2013-12-08 20:59:36 -08:00
|
|
|
pet.use_viewer_data(viewer_data, options)
|
|
|
|
pet
|
|
|
|
end
|
|
|
|
|
2024-01-24 00:51:20 -08:00
|
|
|
# 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, locale=nil)
|
|
|
|
locale ||= I18n.default_locale
|
|
|
|
begin
|
|
|
|
neopets_language_code = I18n.compatible_neopets_language_code_for(locale)
|
|
|
|
envelope = PET_VIEWER.request([name, 0]).post(
|
|
|
|
:timeout => timeout,
|
|
|
|
:headers => {
|
|
|
|
'Cookie' => "lang=#{neopets_language_code}"
|
|
|
|
}
|
|
|
|
)
|
2024-01-24 00:59:11 -08:00
|
|
|
rescue RocketAMFExtensions::RemoteGateway::AMFError => e
|
2024-01-24 00:51:20 -08:00
|
|
|
if e.message == PET_NOT_FOUND_REMOTE_ERROR
|
|
|
|
raise PetNotFound, "Pet #{name.inspect} does not exist"
|
|
|
|
end
|
|
|
|
raise DownloadError, e.message
|
2024-01-24 00:59:11 -08:00
|
|
|
rescue RocketAMFExtensions::RemoteGateway::ConnectionError => e
|
2024-01-24 00:51:20 -08:00
|
|
|
raise DownloadError, e.message, e.backtrace
|
|
|
|
end
|
|
|
|
HashWithIndifferentAccess.new(envelope.messages[0].data.body)
|
|
|
|
end
|
|
|
|
|
2024-01-24 00:54:30 -08:00
|
|
|
class PetNotFound < RuntimeError;end
|
|
|
|
class DownloadError < RuntimeError;end
|
2024-01-24 03:25:23 -08:00
|
|
|
class UnexpectedDataFormat < RuntimeError;end
|
2010-10-07 07:46:23 -07:00
|
|
|
end
|
2011-05-21 19:32:01 -07:00
|
|
|
|