Compare commits
3 commits
9e3ce74ed5
...
1ad3ea8f96
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ad3ea8f96 | |||
| b245690a60 | |||
| 3ed1c46b64 |
9 changed files with 221 additions and 0 deletions
1
Gemfile
1
Gemfile
|
|
@ -90,4 +90,5 @@ gem "solargraph-rails", "~> 1.1", group: :development
|
||||||
# For automated tests.
|
# For automated tests.
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem "rspec-rails", "~> 7.0"
|
gem "rspec-rails", "~> 7.0"
|
||||||
|
gem "webmock", "~> 3.24", group: :test
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,9 @@ GEM
|
||||||
fiber-annotation
|
fiber-annotation
|
||||||
fiber-local (~> 1.1)
|
fiber-local (~> 1.1)
|
||||||
json
|
json
|
||||||
|
crack (1.0.0)
|
||||||
|
bigdecimal
|
||||||
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
csv (3.3.0)
|
csv (3.3.0)
|
||||||
date (3.3.4)
|
date (3.3.4)
|
||||||
|
|
@ -182,6 +185,7 @@ GEM
|
||||||
temple (>= 0.8.2)
|
temple (>= 0.8.2)
|
||||||
thor
|
thor
|
||||||
tilt
|
tilt
|
||||||
|
hashdiff (1.1.2)
|
||||||
hashie (5.0.0)
|
hashie (5.0.0)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.1)
|
||||||
httparty (0.22.0)
|
httparty (0.22.0)
|
||||||
|
|
@ -496,6 +500,10 @@ GEM
|
||||||
activesupport
|
activesupport
|
||||||
faraday (~> 2.0)
|
faraday (~> 2.0)
|
||||||
faraday-follow_redirects
|
faraday-follow_redirects
|
||||||
|
webmock (3.24.0)
|
||||||
|
addressable (>= 2.8.0)
|
||||||
|
crack (>= 0.3.2)
|
||||||
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.8.2)
|
webrick (1.8.2)
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
|
|
@ -549,6 +557,7 @@ DEPENDENCIES
|
||||||
thread-local (~> 1.1)
|
thread-local (~> 1.1)
|
||||||
turbo-rails (~> 2.0)
|
turbo-rails (~> 2.0)
|
||||||
web-console (~> 4.2)
|
web-console (~> 4.2)
|
||||||
|
webmock (~> 3.24)
|
||||||
will_paginate (~> 4.0)
|
will_paginate (~> 4.0)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,10 @@ class AltStyle < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def real_thumbnail_url?
|
||||||
|
thumbnail_url != DEFAULT_THUMBNAIL_URL
|
||||||
|
end
|
||||||
|
|
||||||
# For convenience in the console!
|
# For convenience in the console!
|
||||||
def self.find_by_name(color_name, species_name)
|
def self.find_by_name(color_name, species_name)
|
||||||
color = Color.find_by_name(color_name)
|
color = Color.find_by_name(color_name)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,37 @@ module Neopets::NCMall
|
||||||
uniq
|
uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
|
STYLING_STUDIO_URL = "https://www.neopets.com/np-templates/ajax/stylingstudio/studio.php"
|
||||||
|
def self.load_styles(species_id:, neologin:)
|
||||||
|
Sync do
|
||||||
|
INTERNET.post(
|
||||||
|
STYLING_STUDIO_URL,
|
||||||
|
headers: [
|
||||||
|
["User-Agent", Rails.configuration.user_agent_for_neopets],
|
||||||
|
["Content-Type", "application/x-www-form-urlencoded"],
|
||||||
|
["Cookie", "neologin=#{neologin}"],
|
||||||
|
["X-Requested-With", "XMLHttpRequest"],
|
||||||
|
],
|
||||||
|
body: {tab: 1, mode: "getStyles", species: species_id}.to_query,
|
||||||
|
) do |response|
|
||||||
|
if response.status != 200
|
||||||
|
raise ResponseNotOK.new(response.status),
|
||||||
|
"expected status 200 but got #{response.status} (#{STYLING_STUDIO_URL})"
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
data = JSON.parse(response.read).deep_symbolize_keys
|
||||||
|
|
||||||
|
# HACK: styles is a hash, unless it's empty, in which case it's an
|
||||||
|
# array? Weird. Normalize this by converting to hash.
|
||||||
|
data.fetch(:styles).to_h.values
|
||||||
|
rescue JSON::ParserError, KeyError
|
||||||
|
raise UnexpectedResponseFormat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def self.load_page_by_url(url)
|
def self.load_page_by_url(url)
|
||||||
|
|
|
||||||
87
lib/tasks/alt_styles.rake
Normal file
87
lib/tasks/alt_styles.rake
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
namespace :alt_styles do
|
||||||
|
desc "Import alt style info from the NC Mall"
|
||||||
|
task :import => :environment do
|
||||||
|
neologin = STDIN.getpass("Neologin cookie: ")
|
||||||
|
|
||||||
|
all_species = Species.order(:name).to_a
|
||||||
|
|
||||||
|
# Load 10 species pages from the NC Mall at a time.
|
||||||
|
barrier = Async::Barrier.new
|
||||||
|
semaphore = Async::Semaphore.new(10, parent: barrier)
|
||||||
|
styles_by_species_id = {}
|
||||||
|
Sync do
|
||||||
|
num_loaded = 0
|
||||||
|
num_total = all_species.size
|
||||||
|
print "0/#{num_total} species loaded"
|
||||||
|
|
||||||
|
all_species.each do |species|
|
||||||
|
semaphore.async {
|
||||||
|
begin
|
||||||
|
styles_by_species_id[species.id] = Neopets::NCMall.load_styles(
|
||||||
|
species_id: species.id,
|
||||||
|
neologin:,
|
||||||
|
)
|
||||||
|
rescue => error
|
||||||
|
puts "\n⚠️ Error loading for #{species.human_name}, skipping: #{error.message}"
|
||||||
|
end
|
||||||
|
num_loaded += 1
|
||||||
|
print "\r#{num_loaded}/#{num_total} species loaded"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wait until all tasks are done.
|
||||||
|
barrier.wait
|
||||||
|
ensure
|
||||||
|
barrier.stop # If something goes wrong, clean up all tasks.
|
||||||
|
end
|
||||||
|
print "\n"
|
||||||
|
|
||||||
|
style_ids = styles_by_species_id.values.flatten(1).map { |s| s[:oii] }
|
||||||
|
style_records_by_id =
|
||||||
|
AltStyle.where(id: style_ids).to_h { |as| [as.id, as] }
|
||||||
|
|
||||||
|
all_species.each do |species|
|
||||||
|
styles = styles_by_species_id[species.id]
|
||||||
|
next if styles.nil?
|
||||||
|
|
||||||
|
counts = {changed: 0, unchanged: 0, skipped: 0}
|
||||||
|
styles.each do |style|
|
||||||
|
record = style_records_by_id[style[:oii]]
|
||||||
|
label = "#{style[:name]} (#{style[:oii]})"
|
||||||
|
if record.nil?
|
||||||
|
puts "⚠️ [#{label}]: Not modeled yet, skipping"
|
||||||
|
counts[:skipped] += 1
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if !record.real_thumbnail_url?
|
||||||
|
record.thumbnail_url = style[:image]
|
||||||
|
puts "✅ [#{label}]: Thumbnail URL is now #{style[:image].inspect}"
|
||||||
|
elsif record.thumbnail_url != style[:image]
|
||||||
|
puts "⚠️ [#{label}: Thumbnail URL may have changed, handle manually? " +
|
||||||
|
"#{record.thumbnail_url.inspect} -> #{style[:image].inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
new_series_name = style[:name].match(/\A\S+/)[0] # first word
|
||||||
|
if !record.real_series_name?
|
||||||
|
record.series_name = new_series_name
|
||||||
|
puts "✅ [#{label}]: Series name is now #{new_series_name.inspect}"
|
||||||
|
elsif record.series_name != new_series_name
|
||||||
|
puts "⚠️ [#{label}: Series name may have changed, handle manually? " +
|
||||||
|
"#{record.series_name.inspect} -> #{new_series_name.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if record.changed?
|
||||||
|
counts[:changed] += 1
|
||||||
|
else
|
||||||
|
counts[:unchanged] += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
record.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "#{species.human_name}: #{counts[:changed]} changed, " +
|
||||||
|
"#{counts[:unchanged]} unchanged, #{counts[:skipped]} skipped"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
89
spec/services/nc_mall_spec.rb
Normal file
89
spec/services/nc_mall_spec.rb
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
require 'webmock/rspec'
|
||||||
|
|
||||||
|
RSpec.describe Neopets::NCMall, type: :model do
|
||||||
|
describe ".load_styles" do
|
||||||
|
def stub_styles_request
|
||||||
|
stub_request(:post, "https://www.neopets.com/np-templates/ajax/stylingstudio/studio.php").
|
||||||
|
with(
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"Cookie": "neologin=STUB_NEOLOGIN"
|
||||||
|
},
|
||||||
|
body: "mode=getStyles&species=2&tab=1",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject(:styles) do
|
||||||
|
Neopets::NCMall.load_styles(
|
||||||
|
species_id: 2,
|
||||||
|
neologin: "STUB_NEOLOGIN",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "loads current NC styles from the NC Mall" do
|
||||||
|
stub_styles_request.to_return(
|
||||||
|
body: '{"success":true,"styles":{"87966":{"oii":87966,"name":"Nostalgic Alien Aisha","image":"https:\/\/images.neopets.com\/items\/nostalgic_alien_aisha.gif","limited":false},"87481":{"oii":87481,"name":"Nostalgic Sponge Aisha","image":"https:\/\/images.neopets.com\/items\/nostalgic_sponge_aisha.gif","limited":false},"90031":{"oii":90031,"name":"Celebratory Anniversary Aisha","image":"https:\/\/images.neopets.com\/items\/624dc08bcf.gif","limited":true},"90050":{"oii":90050,"name":"Nostalgic Tyrannian Aisha","image":"https:\/\/images.neopets.com\/items\/b225e06541.gif","limited":true}}}',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).to contain_exactly(
|
||||||
|
{
|
||||||
|
oii: 87481,
|
||||||
|
name: "Nostalgic Sponge Aisha",
|
||||||
|
image: "https://images.neopets.com/items/nostalgic_sponge_aisha.gif",
|
||||||
|
limited: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oii: 87966,
|
||||||
|
name: "Nostalgic Alien Aisha",
|
||||||
|
image: "https://images.neopets.com/items/nostalgic_alien_aisha.gif",
|
||||||
|
limited: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oii: 90031,
|
||||||
|
name: "Celebratory Anniversary Aisha",
|
||||||
|
image: "https://images.neopets.com/items/624dc08bcf.gif",
|
||||||
|
limited: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oii: 90050,
|
||||||
|
name: "Nostalgic Tyrannian Aisha",
|
||||||
|
image: "https://images.neopets.com/items/b225e06541.gif",
|
||||||
|
limited: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles the NC Mall's odd API behavior for zero styles" do
|
||||||
|
stub_styles_request.to_return(
|
||||||
|
# You'd think styles would be `{}` in this case, but it's `[]`. Huh!
|
||||||
|
body: '{"success":true,"styles":[]}',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if the request returns a non-200 status" do
|
||||||
|
stub_styles_request.to_return(status: 400)
|
||||||
|
|
||||||
|
expect { styles }.to raise_error(Neopets::NCMall::ResponseNotOK)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if the request returns a non-JSON response" do
|
||||||
|
stub_styles_request.to_return(
|
||||||
|
body: "Oops, this request failed for some weird reason!",
|
||||||
|
)
|
||||||
|
|
||||||
|
expect { styles }.to raise_error(Neopets::NCMall::UnexpectedResponseFormat)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if the request returns unexpected JSON" do
|
||||||
|
stub_styles_request.to_return(
|
||||||
|
body: '{"success": false}',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect { styles }.to raise_error(Neopets::NCMall::UnexpectedResponseFormat)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
BIN
vendor/cache/crack-1.0.0.gem
vendored
Normal file
BIN
vendor/cache/crack-1.0.0.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/hashdiff-1.1.2.gem
vendored
Normal file
BIN
vendor/cache/hashdiff-1.1.2.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/webmock-3.24.0.gem
vendored
Normal file
BIN
vendor/cache/webmock-3.24.0.gem
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue