From a08fb89d597096689261968b06195401f11131ef Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Fri, 27 Sep 2024 14:14:28 -0700 Subject: [PATCH 1/4] Oops, don't crash when an item has no previews A weird state to get into, one would expect impossible! But something funny is going on with the Kiko Lake Team Popcorn item (85598)! --- app/controllers/items_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index cf56452b..fe390949 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -215,7 +215,8 @@ class ItemsController < ApplicationController @item.compatible_pet_types. preferring_species(cookies["preferred-preview-species-id"] || ""). preferring_color(cookies["preferred-preview-color-id"] || ""). - preferring_simple.first + preferring_simple.first || + PetType.matching_name("Blue", "Acara").first! end def validate_preview From 06a89689d896268d4e997ae052cc52752c3b2d10 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Fri, 27 Sep 2024 15:07:21 -0700 Subject: [PATCH 2/4] Oops, fix crash when modeling Patchwork Staff (AMFPHP string encoding!) See comment for details! I wonder if other items have been affected by this in the past. I think probably what happened before was that we successfully created this item, but failed to create the *translation*, so when migrating over the Patchwork Staff all its translated fields were empty? (That's what I found looking in the database today.) But yeah, thankfully our crash logging at health.openneo.net gave me the name of a pet someone was trying to model, and so I was able to find the bug and fix it! --- app/models/pet.rb | 4 +-- .../remote_gateway/request.rb | 30 ++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/app/models/pet.rb b/app/models/pet.rb index a4aa5214..540a37a3 100644 --- a/app/models/pet.rb +++ b/app/models/pet.rb @@ -168,7 +168,7 @@ class Pet < ApplicationRecord # Return the response body as a `HashWithIndifferentAccess`. def self.send_amfphp_request(request, timeout: 10) begin - response = request.post(timeout: timeout, headers: { + response_data = request.post(timeout: timeout, headers: { "User-Agent" => Rails.configuration.user_agent_for_neopets, }) rescue RocketAMFExtensions::RemoteGateway::AMFError => e @@ -177,7 +177,7 @@ class Pet < ApplicationRecord raise DownloadError, e.message, e.backtrace end - HashWithIndifferentAccess.new(response.messages[0].data.body) + HashWithIndifferentAccess.new(response_data) end end diff --git a/lib/rocketamf_extensions/remote_gateway/request.rb b/lib/rocketamf_extensions/remote_gateway/request.rb index d102b6c3..9adde85a 100644 --- a/lib/rocketamf_extensions/remote_gateway/request.rb +++ b/lib/rocketamf_extensions/remote_gateway/request.rb @@ -54,7 +54,25 @@ module RocketAMFExtensions raise RocketAMF::AMFError.new(first_message_data) end - result + # HACK: It seems to me that these messages come back with Windows-1250 + # (or similar) encoding on the strings? I'm basing this on the + # Patchwork Staff item, whose description arrives as: + # + # "That staff is cute, but dont use it as a walking stick \x96 I " + + # "dont think it will hold you up!" + # + # And the `\x96` is meant to represent an endash, which it doesn't in + # UTF-8 or in most extended ASCII encodings, but *does* in Windows's + # specific extended ASCII. + # + # Idk if this is something to do with the AMFPHP spec or how the AMFPHP + # server code they use serializes strings (I couldn't find any + # reference to it?), or just their internal database encoding being + # passed along as-is, or what? But this seems to be the most correct + # interpretation I know how to do, so, let's do it! + result.messages[0].data.body.tap do |body| + reencode_strings! body, "Windows-1250", "UTF-8" + end end private @@ -86,6 +104,16 @@ module RocketAMFExtensions raise ConnectionError, e.message end end + + def reencode_strings!(target, from, to) + if target.is_a? String + target.force_encoding(from).encode!(to) + elsif target.is_a? Array + target.each { |x| reencode_strings!(x, from, to) } + elsif target.is_a? Hash + target.values.each { |x| reencode_strings!(x, from, to) } + end + end end class ConnectionError < RuntimeError From 5214a14990dd0828eb7220ee41ed57be444d8a19 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Fri, 27 Sep 2024 17:50:35 -0700 Subject: [PATCH 3/4] Rescue from ActiveRecord::ConnectionTimeoutError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just to stop filling the crash logs with it… if they spike, we'll be alerted by the downtime monitor anyway. --- app/controllers/application_controller.rb | 8 +++ public/503.html | 59 +++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 public/503.html diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cc7746e5..15a8cc99 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -21,9 +21,12 @@ class ApplicationController < ActionController::Base class AccessDenied < StandardError; end rescue_from AccessDenied, with: :on_access_denied + rescue_from Async::Stop, Async::Container::Terminate, with: :on_request_stopped + rescue_from ActiveRecord::ConnectionTimeoutError, with: :on_db_timeout + def authenticate_user! redirect_to(new_auth_user_session_path) unless user_signed_in? end @@ -65,6 +68,11 @@ class ApplicationController < ActionController::Base status: :internal_server_error end + def on_db_timeout + render file: 'public/503.html', layout: false, + status: :service_unavailable + end + def redirect_back!(default=:back) redirect_to(params[:return_to] || default) end diff --git a/public/503.html b/public/503.html new file mode 100644 index 00000000..ac499199 --- /dev/null +++ b/public/503.html @@ -0,0 +1,59 @@ + + + + + + Dress to Impress: Whelmy mode! + + + +
+ Distressed Grundo programmer +
+

DTI is overloaded!

+

+ There's a lot going on in our server right now… usually this lasts for + a few seconds, then passes? Sorry about this! +

+

+ If this keeps happening, we'll be alerted automatically, and we'll do + our best to get it fixed up 💖 +

+
+
+ + From d056a5e766c12abf7e431a8e275fda25be4468fd Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Fri, 27 Sep 2024 18:27:12 -0700 Subject: [PATCH 4/4] Oops, don't show not-directly-for-sale items as being "0 NC" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Fall Woodland Leaves Filter" is an example, it's part of the two-item *pack* named "Fall Woodland Minitheus Petpet Foreground". The NC Mall page for it will include the secondary items in `object_data`, but it's not part of the storefront itself—and the only thing indicating that is the `render` list. Theoretically, we could use this to construct more data about like, packs and stuff, automatically? But also, I don't want to backfill it for everything historically, so like. Whatever. --- app/services/nc_mall.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/services/nc_mall.rb b/app/services/nc_mall.rb index 9c914af3..e42df5d0 100644 --- a/app/services/nc_mall.rb +++ b/app/services/nc_mall.rb @@ -76,11 +76,20 @@ module NCMall raise UnexpectedResponseFormat, "missing field object_data in NC page" end + object_data = nc_page["object_data"] + # NOTE: When there's no object data, it will be an empty array instead of # an empty hash. Weird API thing to work around! - nc_page["object_data"] = {} if nc_page["object_data"] == [] + object_data = {} if object_data == [] - items = nc_page["object_data"].values.map do |item_info| + # Only the items in the `render` list are actually listed as directly for + # sale in the shop. `object_data` might contain other items that provide + # supporting information about them, but aren't actually for sale. + visible_object_data = (nc_page["render"] || []). + map { |id| object_data[id.to_s] }. + filter(&:present?) + + items = visible_object_data.map do |item_info| { id: item_info["id"], name: item_info["name"],