Compare commits

..

No commits in common. "e511bdc5e2dca96c8daa4acd35aaac7a80a7d540" and "610177d3f5276ca675ac14ee678e586fd0dff274" have entirely different histories.

17 changed files with 59 additions and 179 deletions

View file

@ -11,8 +11,7 @@ class ContributionsController < ApplicationController
@contributions, @contributions,
:scopes => { :scopes => {
'Item' => Item.includes(:translations), 'Item' => Item.includes(:translations),
'PetType' => PetType.includes(:species, :color), 'PetType' => PetType.includes(:species, :color)
'AltStyle' => AltStyle.includes(:species, :color),
} }
) )
end end

View file

@ -1,13 +1,10 @@
class PetsController < ApplicationController class PetsController < ApplicationController
rescue_from Pet::PetNotFound, with: :pet_not_found rescue_from Pet::PetNotFound, :with => :pet_not_found
rescue_from PetType::DownloadError, SwfAsset::DownloadError, with: :asset_download_error rescue_from PetType::DownloadError, SwfAsset::DownloadError, :with => :asset_download_error
rescue_from Pet::DownloadError, with: :pet_download_error rescue_from Pet::DownloadError, :with => :pet_download_error
rescue_from Pet::UnexpectedDataFormat, with: :unexpected_data_format rescue_from Pet::ModelingDisabled, with: :modeling_disabled
def load def load
# Uncomment this to temporarily disable modeling for most users.
# return modeling_disabled unless user_signed_in? && current_user.admin?
raise Pet::PetNotFound unless params[:name] raise Pet::PetNotFound unless params[:name]
@pet = Pet.load( @pet = Pet.load(
params[:name], params[:name],
@ -83,9 +80,4 @@ class PetsController < ApplicationController
pet_load_error long_message: t('pets.load.modeling_disabled'), pet_load_error long_message: t('pets.load.modeling_disabled'),
status: :forbidden status: :forbidden
end end
def unexpected_data_format
pet_load_error long_message: t('pets.load.unexpected_data_format'),
status: :internal_server_error
end
end end

View file

@ -9,8 +9,6 @@ module ContributionHelper
contributed_pet_type('pet_type', contributed, show_image) contributed_pet_type('pet_type', contributed, show_image)
when PetState when PetState
contributed_pet_type('pet_state', contributed.pet_type, show_image) contributed_pet_type('pet_state', contributed.pet_type, show_image)
when AltStyle
contributed_alt_style(contributed, show_image)
end end
end end
@ -39,20 +37,6 @@ module ContributionHelper
output output
end end
def contributed_alt_style(alt_style, show_image)
span = content_tag(:span, alt_style.name, class: 'contributed-name')
output = translate("contributions.contributed_description.main.alt_style_html",
alt_style_name: span)
# HACK: Just assume this is a Nostalgic Alt Style, and that the thumbnail
# is named reliably!
if show_image
thumbnail_url = "https://images.neopets.com/items/nostalgic_" +
"#{alt_style.color.name.downcase}_#{alt_style.species.name.downcase}.gif"
output << image_tag(thumbnail_url)
end
output
end
private private
def output(&block) def output(&block)

View file

@ -1,22 +0,0 @@
class AltStyle < ApplicationRecord
belongs_to :species
belongs_to :color
has_many :parent_swf_asset_relationships, as: :parent
has_many :swf_assets, through: :parent_swf_asset_relationships
has_many :contributions, as: :contributed, inverse_of: :contributed
def name
I18n.translate('pet_types.human_name', color_human_name: color.human_name,
species_human_name: species.human_name)
end
def biology=(biology)
# TODO: This is very similar to what `PetState` does, but like… much much
# more compact? Idk if I'm missing something, or if I was just that much
# more clueless back when I wrote it, lol 😅
self.swf_assets = biology.values.map do |asset_data|
SwfAsset.from_biology_data(self.body_id, asset_data)
end
end
end

View file

@ -3,8 +3,7 @@ class Contribution < ApplicationRecord
'Item' => 3, 'Item' => 3,
'SwfAsset' => 2, 'SwfAsset' => 2,
'PetType' => 15, 'PetType' => 15,
'PetState' => 10, 'PetState' => 10
'AltStyle' => 30,
} }
belongs_to :contributed, :polymorphic => true belongs_to :contributed, :polymorphic => true
@ -25,7 +24,7 @@ class Contribution < ApplicationRecord
'SwfAsset' => 'Item', 'SwfAsset' => 'Item',
'PetState' => 'PetType' 'PetState' => 'PetType'
} }
CONTRIBUTED_CHILDREN = CONTRIBUTED_RELATIONSHIPS.keys + ['AltStyle'] CONTRIBUTED_CHILDREN = CONTRIBUTED_RELATIONSHIPS.keys
CONTRIBUTED_TYPES = CONTRIBUTED_CHILDREN + CONTRIBUTED_RELATIONSHIPS.values CONTRIBUTED_TYPES = CONTRIBUTED_CHILDREN + CONTRIBUTED_RELATIONSHIPS.values
def self.preload_contributeds_and_parents(contributions, options={}) def self.preload_contributeds_and_parents(contributions, options={})
options[:scopes] ||= {} options[:scopes] ||= {}

View file

@ -1,29 +1,28 @@
require 'rocketamf_extensions/remote_gateway' require 'rocketamf/remote_gateway'
require 'ostruct' 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). PET_VIEWER = RocketAMF::RemoteGateway.new(GATEWAY_URL).
service('CustomPetService').action('getViewerData') service('CustomPetService').action('getViewerData')
PET_NOT_FOUND_REMOTE_ERROR = 'PHP: Unable to retrieve records from the database.' PET_NOT_FOUND_REMOTE_ERROR = 'PHP: Unable to retrieve records from the database.'
WARDROBE_PATH = '/wardrobe' WARDROBE_PATH = '/wardrobe'
belongs_to :pet_type belongs_to :pet_type
attr_reader :items, :pet_state, :alt_style attr_reader :items, :pet_state
scope :with_pet_type_color_ids, ->(color_ids) { scope :with_pet_type_color_ids, ->(color_ids) {
joins(:pet_type).where(PetType.arel_table[:id].in(color_ids)) joins(:pet_type).where(PetType.arel_table[:id].in(color_ids))
} }
class ModelingDisabled < RuntimeError;end
def load!(options={}) def load!(options={})
raise ModelingDisabled
options[:locale] ||= I18n.default_locale options[:locale] ||= I18n.default_locale
I18n.with_locale(options.delete(:locale)) do I18n.with_locale(options.delete(:locale)) do
use_viewer_data( use_viewer_data(fetch_viewer_data(options.delete(:timeout)), options)
self.class.fetch_viewer_data(name, options.delete(:timeout)),
options,
)
end end
true true
end end
@ -33,41 +32,44 @@ class Pet < ApplicationRecord
pet_data = viewer_data[:custom_pet] pet_data = viewer_data[:custom_pet]
raise UnexpectedDataFormat unless pet_data[:species_id]
raise UnexpectedDataFormat unless pet_data[:color_id]
raise UnexpectedDataFormat unless pet_data[:body_id]
self.pet_type = PetType.find_or_initialize_by( self.pet_type = PetType.find_or_initialize_by(
species_id: pet_data[:species_id].to_i, species_id: pet_data[:species_id].to_i,
color_id: pet_data[:color_id].to_i color_id: pet_data[:color_id].to_i
) )
self.pet_type.body_id = pet_data[:body_id] self.pet_type.body_id = pet_data[:body_id]
self.pet_type.origin_pet = self self.pet_type.origin_pet = self
biology = pet_data[:biology_by_zone]
pet_state_biology = pet_data[:alt_style] ? biology[0] = nil # remove effects if present
pet_data[:original_biology] : pet_data[:biology_by_zone] @pet_state = self.pet_type.add_pet_state_from_biology! biology
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?
@alt_style = AltStyle.find_or_initialize_by(id: pet_data[:alt_style].to_i)
@alt_style.assign_attributes(
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
@items = Item.collection_from_pet_type_and_registries(self.pet_type, @items = Item.collection_from_pet_type_and_registries(self.pet_type,
viewer_data[:object_info_registry], viewer_data[:object_asset_registry], viewer_data[:object_info_registry], viewer_data[:object_asset_registry],
options[:item_scope]) options[:item_scope])
end 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 fetch_viewer_data(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}"
}
)
rescue RocketAMF::RemoteGateway::AMFError => e
if e.message == PET_NOT_FOUND_REMOTE_ERROR
raise PetNotFound, "Pet #{name.inspect} does not exist"
end
raise DownloadError, e.message
rescue RocketAMF::RemoteGateway::ConnectionError => e
raise DownloadError, e.message, e.backtrace
end
HashWithIndifferentAccess.new(envelope.messages[0].data.body)
end
def wardrobe_query def wardrobe_query
{ {
:name => self.name, :name => self.name,
@ -79,7 +81,7 @@ class Pet < ApplicationRecord
end end
def contributables def contributables
contributables = [pet_type, @pet_state, @alt_style].filter(&:present?) contributables = [pet_type, @pet_state]
items.each do |item| items.each do |item|
contributables << item contributables << item
contributables += item.pending_swf_assets contributables += item.pending_swf_assets
@ -100,10 +102,6 @@ class Pet < ApplicationRecord
item.handle_assets! item.handle_assets!
end end
end end
if @alt_style
@alt_style.save!
end
end end
def self.load(name, options={}) def self.load(name, options={})
@ -118,32 +116,7 @@ class Pet < ApplicationRecord
pet pet
end end
# NOTE: Ideally pet requests shouldn't take this long, but Neopets can be class PetNotFound < Exception;end
# slow sometimes! Since we're on the Falcon server, long timeouts shouldn't class DownloadError < Exception;end
# 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}"
}
)
rescue RocketAMFExtensions::RemoteGateway::AMFError => e
if e.message == PET_NOT_FOUND_REMOTE_ERROR
raise PetNotFound, "Pet #{name.inspect} does not exist"
end
raise DownloadError, e.message
rescue RocketAMFExtensions::RemoteGateway::ConnectionError => e
raise DownloadError, e.message, e.backtrace
end
HashWithIndifferentAccess.new(envelope.messages[0].data.body)
end
class PetNotFound < RuntimeError;end
class DownloadError < RuntimeError;end
class UnexpectedDataFormat < RuntimeError;end
end end

View file

@ -180,15 +180,6 @@ class SwfAsset < ApplicationRecord
self.manifest_url = parsed_manifest_url.to_s self.manifest_url = parsed_manifest_url.to_s
end end
def self.from_biology_data(body_id, data)
remote_id = data[:part_id].to_i
swf_asset = SwfAsset.find_or_initialize_by type: 'biology',
remote_id: remote_id
swf_asset.body_id = body_id
swf_asset.origin_biology_data = data
swf_asset
end
def self.from_wardrobe_link_params(ids) def self.from_wardrobe_link_params(ids)
where(( where((
arel_table[:remote_id].in(ids[:biology]).and(arel_table[:type].eq('biology')) arel_table[:remote_id].in(ids[:biology]).and(arel_table[:type].eq('biology'))

View file

@ -2,12 +2,10 @@
= advertise_campaign_progress @campaign = advertise_campaign_progress @campaign
.notice .warning
%strong Happy NC UC day! %strong Happy NC UC day!
We're still working on Alt Styles support, but other pets can be loaded as We've temporarily disabled pet loading while we get everything set up and
usual! investigate some new compatibility issues. We'll have it back soon!
%br
Excited to have them for you soon!
%p#pet-not-found.alert= t 'pets.load.not_found' %p#pet-not-found.alert= t 'pets.load.not_found'

View file

@ -12,7 +12,7 @@ Rack::Attack.throttled_responder = lambda do |req|
if req.path.end_with?('.json') if req.path.end_with?('.json')
[503, {}, [PETS_THROTTLE_MESSAGE]] [503, {}, [PETS_THROTTLE_MESSAGE]]
else else
flash = req.env['action_dispatch.request.flash_hash'] flash = req.flash
flash[:warning] = PETS_THROTTLE_MESSAGE flash[:warning] = PETS_THROTTLE_MESSAGE
[302, {"Location" => "/"}, [PETS_THROTTLE_MESSAGE]] [302, {"Location" => "/"}, [PETS_THROTTLE_MESSAGE]]
end end

View file

@ -232,7 +232,6 @@ en:
swf_asset_html: "%{item_description} on a new body type" swf_asset_html: "%{item_description} on a new body type"
pet_type_html: "%{pet_type_description} for the first time" pet_type_html: "%{pet_type_description} for the first time"
pet_state_html: "a new pose for %{pet_type_description}" pet_state_html: "a new pose for %{pet_type_description}"
alt_style_html: "a new Alt Style of the %{alt_style_name}"
contribution: contribution:
description_html: "%{user_link} showed us %{contributed_description}" description_html: "%{user_link} showed us %{contributed_description}"
@ -785,10 +784,6 @@ en:
modeling_disabled: We've turned off pet loading for a bit, while we modeling_disabled: We've turned off pet loading for a bit, while we
investigate some changes in how it works. We'll be back as soon as we investigate some changes in how it works. We'll be back as soon as we
can! can!
unexpected_data_format:
We found the pet and what it's wearing, but the data isn't in quite the
format we usually expect, so we're stopping to make sure we don't make
a mistake. Sorry about this—if it keeps happening, let us know!
swf_assets: swf_assets:
links: links:

View file

@ -1,11 +0,0 @@
class CreateAltStyles < ActiveRecord::Migration[7.1]
def change
create_table :alt_styles do |t|
t.references :species, type: :integer, null: false, foreign_key: true
t.references :color, type: :integer, null: false, foreign_key: true
t.integer :body_id, null: false
t.timestamps
end
end
end

View file

@ -10,17 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_01_24_102340) do ActiveRecord::Schema[7.1].define(version: 2024_01_23_133215) do
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "species_id", null: false
t.integer "color_id", null: false
t.integer "body_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["color_id"], name: "index_alt_styles_on_color_id"
t.index ["species_id"], name: "index_alt_styles_on_species_id"
end
create_table "auth_servers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t| create_table "auth_servers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
t.string "short_name", limit: 10, null: false t.string "short_name", limit: 10, null: false
t.string "name", limit: 40, null: false t.string "name", limit: 40, null: false
@ -311,6 +301,4 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_24_102340) do
t.string "plain_label", null: false t.string "plain_label", null: false
end end
add_foreign_key "alt_styles", "colors"
add_foreign_key "alt_styles", "species"
end end

View file

@ -1,8 +1,8 @@
require 'net/http' require 'net/http'
require 'rocketamf' require 'rocketamf'
require_relative 'remote_gateway/service' require File.join(File.dirname(__FILE__), 'remote_gateway', 'service')
module RocketAMFExtensions module RocketAMF
class RemoteGateway class RemoteGateway
attr_reader :uri attr_reader :uri

View file

@ -1,6 +1,6 @@
require_relative 'request' require File.join(File.dirname(__FILE__), 'request')
module RocketAMFExtensions module RocketAMF
class RemoteGateway class RemoteGateway
class Action class Action
attr_reader :service, :name attr_reader :service, :name

View file

@ -1,6 +1,6 @@
require 'timeout' require 'timeout'
module RocketAMFExtensions module RocketAMF
class RemoteGateway class RemoteGateway
class Request class Request
ERROR_CODE = 'AMFPHP_RUNTIME_ERROR' ERROR_CODE = 'AMFPHP_RUNTIME_ERROR'
@ -51,7 +51,7 @@ module RocketAMFExtensions
first_message_data = HashWithIndifferentAccess.new(result.messages[0].data) first_message_data = HashWithIndifferentAccess.new(result.messages[0].data)
if first_message_data.respond_to?(:[]) && first_message_data[:code] == ERROR_CODE if first_message_data.respond_to?(:[]) && first_message_data[:code] == ERROR_CODE
raise RocketAMF::AMFError.new(first_message_data) raise AMFError.new(first_message_data)
end end
result result
@ -60,17 +60,17 @@ module RocketAMFExtensions
private private
def envelope def envelope
output = RocketAMF::Envelope.new output = Envelope.new
output.messages << wrapper_message output.messages << wrapper_message
output output
end end
def wrapper_message def wrapper_message
message = RocketAMF::Message.new 'null', '/1', [remoting_message] message = Message.new 'null', '/1', [remoting_message]
end end
def remoting_message def remoting_message
message = RocketAMF::Values::RemotingMessage.new message = Values::RemotingMessage.new
message.source = @action.service.name message.source = @action.service.name
message.operation = @action.name message.operation = @action.name
message.body = @params message.body = @params

View file

@ -1,6 +1,6 @@
require_relative 'action' require File.join(File.dirname(__FILE__), 'action')
module RocketAMFExtensions module RocketAMF
class RemoteGateway class RemoteGateway
class Service class Service
attr_reader :gateway, :name attr_reader :gateway, :name

View file

@ -1,6 +0,0 @@
namespace :pets do
desc "Load a pet's viewer data"
task :load, [:name] => [:environment] do |task, args|
pp Pet.fetch_viewer_data(args[:name])
end
end