Merge branch 'closet'

This commit is contained in:
Emi Matchu 2011-08-02 22:42:59 -04:00
commit 2dd6586ea6
10 changed files with 236 additions and 138 deletions

View file

@ -1,67 +0,0 @@
class ClosetPagesController < ApplicationController
include ActionView::Helpers::TextHelper
before_filter :authenticate_user!, :build_closet_page
rescue_from ClosetPage::ParseError, :with => :on_parse_error
def create
if params[:closet_page] && params[:closet_page][:source]
@closet_page.index = params[:closet_page][:index]
@closet_page.source = params[:closet_page][:source]
saved_counts = @closet_page.save_hangers!
any_created = saved_counts[:created] > 0
any_updated = saved_counts[:updated] > 0
if any_created || any_updated
message = "Page #{@closet_page.index} saved! We "
message << "added " + pluralize(saved_counts[:created], 'item') + " to your closet" if any_created
message << " and " if any_created && any_updated
message << "updated the count on " + pluralize(saved_counts[:updated], 'item') if any_updated
message << ". "
else
message = "Success! We checked that page, and we already had all this data recorded. "
end
unless @closet_page.unknown_item_names.empty?
message << "We also found " +
pluralize(@closet_page.unknown_item_names.size, 'item') +
" we didn't recognize: " +
@closet_page.unknown_item_names.to_sentence +
". Please put each item on your pet and type its name in on the " +
"home page so we can have a record of it. Thanks! "
end
if @closet_page.last?
message << "That was the last page of your Neopets closet."
destination = user_closet_hangers_path(current_user)
else
message << "Now the frame should contain page #{@closet_page.index + 1}. Paste that source code over, too."
destination = {:action => :new, :index => (@closet_page.index + 1)}
end
flash[:success] = message
redirect_to destination
else
redirect_to :action => :new
end
end
def new
@closet_page.index ||= 1
end
protected
def build_closet_page
@closet_page = ClosetPage.new(current_user)
@closet_page.index = params[:index]
end
def on_parse_error
flash[:alert] = "We had trouble reading your source code. Is it a valid HTML document? Make sure you pasted the computery-looking result of clicking View Frame Source, and not the pretty page itself."
render :action => :new
end
end

View file

@ -0,0 +1,79 @@
class NeopetsPagesController < ApplicationController
include ActionView::Helpers::TextHelper
before_filter :authenticate_user!, :build_neopets_page
rescue_from ClosetPage::ParseError, :with => :on_parse_error
def create
if @page_params && @page_params[:source]
@neopets_page.index = @page_params[:index]
@neopets_page.source = @page_params[:source]
saved_counts = @neopets_page.save_hangers!
any_created = saved_counts[:created] > 0
any_updated = saved_counts[:updated] > 0
if any_created || any_updated
message = "Page #{@neopets_page.index} saved! We "
message << "added " + pluralize(saved_counts[:created], 'item') + " to the items you own" if any_created
message << " and " if any_created && any_updated
message << "updated the count on " + pluralize(saved_counts[:updated], 'item') if any_updated
message << ". "
elsif @neopets_page.hangers.size > 1
message = "Success! We checked that page, and we already had all this data recorded. "
else
message = "Success! We checked that page, and there were no wearables to add. "
end
unless @neopets_page.unknown_item_names.empty?
message << "We also found " +
pluralize(@neopets_page.unknown_item_names.size, 'item') +
" we didn't recognize: " +
@neopets_page.unknown_item_names.to_sentence +
". Please put each item on your pet and type its name in on the " +
"home page so we can have a record of it. Thanks! "
end
if @neopets_page.last?
message << "That was the last page of your Neopets #{@neopets_page.name}."
destination = user_closet_hangers_path(current_user)
else
message << "Now the frame should contain page #{@neopets_page.index + 1}. Paste that source code over, too."
destination = {:action => :new, :index => (@neopets_page.index + 1)}
end
flash[:success] = message
redirect_to destination
else
redirect_to :action => :new
end
end
def new
@neopets_page.index ||= 1
end
protected
TYPES = {
'closet' => ClosetPage,
'sdb' => SafetyDepositPage
}
def build_neopets_page
type_class = TYPES[params[:type]]
@neopets_page = type_class.new(current_user)
@neopets_page.index = params[:index]
@page_params = params[type_class.model_name.singular]
end
def on_parse_error(e)
Rails.logger.info "Neopets page parse error: #{e.message}"
flash[:alert] = "We had trouble reading your source code. Is it a valid " +
"HTML document? Make sure you pasted the computery-looking result of " +
"clicking View Frame Source, and not the pretty page itself. "
render :action => :new
end
end

View file

@ -4,7 +4,7 @@ class ClosetPage
include ActiveModel::Conversion include ActiveModel::Conversion
extend ActiveModel::Naming extend ActiveModel::Naming
SELECTORS = { @selectors = {
:items => "form[action=\"process_closet.phtml\"] tr[bgcolor!=silver][bgcolor!=\"#E4E4E4\"]", :items => "form[action=\"process_closet.phtml\"] tr[bgcolor!=silver][bgcolor!=\"#E4E4E4\"]",
:item_thumbnail => "img", :item_thumbnail => "img",
:item_name => "td:nth-child(2)", :item_name => "td:nth-child(2)",
@ -26,6 +26,10 @@ class ClosetPage
@index == @total_pages @index == @total_pages
end end
def name
'closet'
end
def persisted? def persisted?
false false
end end
@ -58,47 +62,79 @@ class ClosetPage
protected protected
def element(selector_name, parent) def element(selector_name, parent)
parent.at_css(SELECTORS[selector_name]) || parent.at_css(self.class.selectors[selector_name]) ||
raise(ParseError, "Closet #{selector_name} element not found in #{parent.inspect}") raise(ParseError, "#{selector_name} element not found")
end end
def elements(selector_name, parent) def elements(selector_name, parent)
parent.css(SELECTORS[selector_name]) parent.css(self.class.selectors[selector_name])
end
def find_id(row)
element(:item_remove, row)['name']
end
def find_index(page_selector)
element(:selected, page_selector)['value'].to_i
end
def find_items(doc)
elements(:items, doc)
end
def find_name(row)
# For normal items, the td contains essentially:
# <b>NAME<br/><span>OPTIONAL ADJECTIVE</span></b>
# For PB items, the td contains:
# NAME<br/><span>OPTIONAL ADJECTIVE</span>
# So, we want the first text node. If it's a PB item, that's the first
# child. If it's a normal item, it's the first child <b>'s child.
name_el = element(:item_name, row).children[0]
name_el = name_el.children[0] if name_el.name == 'b'
name_el.text
end
def find_page_selector(doc)
element(:page_select, doc)
end
def find_quantity(row)
element(:item_quantity, row).text.to_i
end
def find_thumbnail_url(row)
element(:item_thumbnail, row)['src']
end
def find_total_pages(page_selector)
page_selector.children.size
end end
def parse_source!(source) def parse_source!(source)
doc = Nokogiri::HTML(source) doc = Nokogiri::HTML(source)
page_selector = element(:page_select, doc) page_selector = find_page_selector(doc)
@total_pages = page_selector.children.size @total_pages = find_total_pages(page_selector)
@index = element(:selected, page_selector)['value'].to_i @index = find_index(page_selector)
items_data = { items_data = {
:id => {}, :id => {},
:thumbnail_url => {} :thumbnail_url => {}
} }
# Go through the items, and find the ID/thumbnail for each and data with it
elements(:items, doc).each do |row|
# For normal items, the td contains essentially:
# <b>NAME<br/><span>OPTIONAL ADJECTIVE</span></b>
# For PB items, the td contains:
# NAME<br/><span>OPTIONAL ADJECTIVE</span>
# So, we want the first text node. If it's a PB item, that's the first
# child. If it's a normal item, it's the first child <b>'s child.
name_el = element(:item_name, row).children[0]
name_el = name_el.children[0] if name_el.name == 'b'
# Go through the items, and find the ID/thumbnail for each and data with it
find_items(doc).each do |row|
data = { data = {
:name => name_el.text, :name => find_name(row),
:quantity => element(:item_quantity, row).text.to_i :quantity => find_quantity(row)
} }
if id = element(:item_remove, row)['name'] if id = find_id(row)
id = id.to_i id = id.to_i
items_data[:id][id] = data items_data[:id][id] = data
else # if this is a pb item, which does not give ID, go by thumbnail else # if this is a pb item, which does not give ID, go by thumbnail
thumbnail_url = element(:item_thumbnail, row)['src'] thumbnail_url = find_thumbnail_url(row)
items_data[:thumbnail_url][thumbnail_url] = data items_data[:thumbnail_url][thumbnail_url] = data
end end
end end
@ -133,6 +169,10 @@ class ClosetPage
end end
end end
def self.selectors
@selectors
end
class ParseError < RuntimeError;end class ParseError < RuntimeError;end
end end

View file

@ -0,0 +1,39 @@
class SafetyDepositPage < ClosetPage
@selectors = {
:items => "#content tr[bgcolor=\"#DFEAF7\"]",
:item_thumbnail => "img",
:item_name => "td:nth-child(2)",
:item_quantity => "td:nth-child(5)",
:item_remove => "input",
:page_select => "select[name=offset]",
:selected => "option[selected]"
}
def name
'SDB'
end
def url
"http://www.neopets.com/safetydeposit.phtml?offset=#{offset}"
end
protected
REMOVE_NAME_REGEX = /\[([0-9]+)\]/
def find_id(*args)
name = super
unless match = name.match(REMOVE_NAME_REGEX)
raise ParseError, "Remove Item input name format was unexpected: #{name}.inspect"
end
match[1]
end
def find_index(*args)
(super / 30) + 1
end
def offset
@index ? (@index.to_i - 1) * 30 : 0
end
end

View file

@ -1,4 +1,4 @@
body.closet_pages-new, body.closet_pages-create body.neopets_pages-new, body.neopets_pages-create
#title #title
float: left float: left

View file

@ -9,7 +9,7 @@
@import closet_hangers/index @import closet_hangers/index
@import closet_hangers/petpage @import closet_hangers/petpage
@import closet_lists/form @import closet_lists/form
@import closet_pages/new @import neopets_pages/new
@import contributions/index @import contributions/index
@import items @import items
@import items/index @import items/index

View file

@ -74,8 +74,9 @@
%label{:for => 'closet-hangers-share-box'} Public URL: %label{:for => 'closet-hangers-share-box'} Public URL:
%input#closet-hangers-share-box{:type => 'text', :value => user_closet_hangers_url(@user), :readonly => true} %input#closet-hangers-share-box{:type => 'text', :value => user_closet_hangers_url(@user), :readonly => true}
= link_to "Import from Neopets closet", new_closet_page_path = link_to "Import from closet", new_closet_page_path
= link_to "Export to Neopets petpage", petpage_user_closet_hangers_path(@user) = link_to "Import from SDB", new_safety_deposit_page_path
= link_to "Export to petpage", petpage_user_closet_hangers_path(@user)

View file

@ -1,26 +1,26 @@
- title "Import from closet, Page #{@closet_page.index}" - title "Import from #{@neopets_page.name}, Page #{@neopets_page.index}"
- content_for :before_flashes do - content_for :before_flashes do
= link_to 'Back to Your Items', user_closet_hangers_path(current_user), :id => 'back-to-items' = link_to 'Back to Your Items', user_closet_hangers_path(current_user), :id => 'back-to-items'
= form_for @closet_page, :html => {:id => 'closet-page-form'} do |f| = form_for @neopets_page, :html => {:id => 'closet-page-form'} do |f|
= f.hidden_field :index = f.hidden_field :index
#closet-page-frame-wrapper #closet-page-frame-wrapper
%span %span
%strong Page #{@closet_page.index} %strong Page #{@neopets_page.index}
of your closet of your #{@neopets_page.name}
%iframe#closet-page-frame{:src => @closet_page.url} %iframe#closet-page-frame{:src => @neopets_page.url}
#closet-page-source #closet-page-source
= f.label :source, "Paste source code below" = f.label :source, "Paste source code below"
= f.text_area :source = f.text_area :source
= f.submit 'Add items to closet' = f.submit 'Import items'
:markdown :markdown
**Welcome to the bulk closet importer!** We're going to make it as **Welcome to the bulk #{@neopets_page.name} importer!** We're going to make it as
easy as possible to import your Neopets.com closet data into your Dress to easy as possible to import your Neopets.com #{@neopets_page.name} data into your Dress to
Impress closet. Here's how it works. Impress items list. Here's how it works.
1. Check the framed Neopets.com window on the left, pointing to 1. Check the framed Neopets.com window on the left, pointing to
[page #{@closet_page.index} of your closet][cp]. [page #{@neopets_page.index} of your #{@neopets_page.name}][cp].
* **Confirm that you're logged in.** If you're logged into * **Confirm that you're logged in.** If you're logged into
Neopets, but the above frame says that you're not, try enabling Neopets, but the above frame says that you're not, try enabling
"third-party cookies" in your browser. (Most have that on by default.) "third-party cookies" in your browser. (Most have that on by default.)
@ -29,7 +29,7 @@
web programmer pro who can check that the frame does, in fact, point web programmer pro who can check that the frame does, in fact, point
to Neopets.com. To be safe, to Neopets.com. To be safe,
#{link_to_neopets_login "pull up another window, check the URL, and log in safely"}. #{link_to_neopets_login "pull up another window, check the URL, and log in safely"}.
* **Confirm that the page is, in fact, your closet.** Similarly, don't * **Confirm that the page is, in fact, your #{@neopets_page.name}.** Similarly, don't
just trust a website when they tell you to copy-paste the source code just trust a website when they tell you to copy-paste the source code
of another site. Instead, check that the page is what it is supposed to of another site. Instead, check that the page is what it is supposed to
be and does not contain any information you didn't mean to give out. be and does not contain any information you didn't mean to give out.
@ -39,7 +39,7 @@
* **In Firefox,** right-click the frame, choose **This Frame**, then **View Frame Source**. * **In Firefox,** right-click the frame, choose **This Frame**, then **View Frame Source**.
* In other browsers, right-click, and look for something similar. If you're * In other browsers, right-click, and look for something similar. If you're
still having trouble, try still having trouble, try
#{link_to "viewing the page in a new window", @closet_page.url, :target => "_blank"}, #{link_to "viewing the page in a new window", @neopets_page.url, :target => "_blank"},
right-clicking, and choosing View Source. right-clicking, and choosing View Source.
3. Highlight the entire source code, and copy-paste it into the box on the right. 3. Highlight the entire source code, and copy-paste it into the box on the right.
@ -48,10 +48,10 @@
4. Submit! 4. Submit!
* We'll analyze the code you sent us, grab exclusively the identity and * We'll analyze the code you sent us, grab exclusively the identity and
quantity of items in your closet, and add that to your Dress to Impress quantity of items in your #{@neopets_page.name}, and add that to your Dress to Impress
closet. I promise it's all safe, but, if you're concerned, find a items list. I promise it's all safe, but, if you're concerned, find a
programmer buddy and [check out the source code to be sure][source]. programmer buddy and [check out the source code to be sure][source].
[cp]: #{@closet_page.url} [cp]: #{@neopets_page.url}
[source]: http://github.com/matchu/openneo-impress-rails [source]: http://github.com/matchu/openneo-impress-rails

View file

@ -30,7 +30,13 @@ OpenneoImpressItems::Application.routes.draw do |map|
resources :pet_attributes, :only => [:index] resources :pet_attributes, :only => [:index]
resources :swf_assets, :only => [:index, :show] resources :swf_assets, :only => [:index, :show]
resources :closet_pages, :only => [:new, :create], :path => 'closet/pages' scope 'import' do
resources :closet_pages, :only => [:new, :create],
:controller => 'neopets_pages', :path => 'closet/pages', :type => 'closet'
resources :safety_deposit_pages, :only => [:new, :create],
:controller => 'neopets_pages', :path => 'sdb/pages', :type => 'sdb'
end
match '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits match '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits

View file

@ -1348,16 +1348,16 @@ body.closet_lists-new form ul.fields .hint, body.closet_lists-create form ul.fie
font-size: 85%; font-size: 85%;
} }
/* line 3, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 3, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #title, body.closet_pages-create #title { body.neopets_pages-new #title, body.neopets_pages-create #title {
float: left; float: left;
} }
/* line 6, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 6, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new .flash, body.closet_pages-create .flash { body.neopets_pages-new .flash, body.neopets_pages-create .flash {
clear: both; clear: both;
} }
/* line 9, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 9, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #back-to-items, body.closet_pages-create #back-to-items { body.neopets_pages-new #back-to-items, body.neopets_pages-create #back-to-items {
/* http://www.zurb.com/blog_uploads/0000/0617/buttons-03.html */ /* http://www.zurb.com/blog_uploads/0000/0617/buttons-03.html */
-moz-border-radius: 5px; -moz-border-radius: 5px;
-webkit-border-radius: 5px; -webkit-border-radius: 5px;
@ -1378,69 +1378,69 @@ body.closet_pages-new #back-to-items, body.closet_pages-create #back-to-items {
margin-top: 0.75em; margin-top: 0.75em;
} }
/* line 34, ../../../app/stylesheets/partials/clean/_mixins.sass */ /* line 34, ../../../app/stylesheets/partials/clean/_mixins.sass */
body.closet_pages-new #back-to-items:hover, body.closet_pages-create #back-to-items:hover { body.neopets_pages-new #back-to-items:hover, body.neopets_pages-create #back-to-items:hover {
background-color: #005300; background-color: #005300;
} }
/* line 53, ../../../app/stylesheets/partials/clean/_mixins.sass */ /* line 53, ../../../app/stylesheets/partials/clean/_mixins.sass */
body.closet_pages-new #back-to-items:hover, body.closet_pages-create #back-to-items:hover { body.neopets_pages-new #back-to-items:hover, body.neopets_pages-create #back-to-items:hover {
color: white; color: white;
} }
/* line 55, ../../../app/stylesheets/partials/clean/_mixins.sass */ /* line 55, ../../../app/stylesheets/partials/clean/_mixins.sass */
body.closet_pages-new #back-to-items:active, body.closet_pages-create #back-to-items:active { body.neopets_pages-new #back-to-items:active, body.neopets_pages-create #back-to-items:active {
top: 1px; top: 1px;
} }
/* line 15, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 15, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #closet-page-form, body.closet_pages-create #closet-page-form { body.neopets_pages-new #closet-page-form, body.neopets_pages-create #closet-page-form {
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
clear: both; clear: both;
margin-bottom: 1em; margin-bottom: 1em;
} }
/* line 8, ../../../app/stylesheets/partials/clean/_mixins.sass */ /* line 8, ../../../app/stylesheets/partials/clean/_mixins.sass */
body.closet_pages-new #closet-page-form, body.closet_pages-create #closet-page-form { body.neopets_pages-new #closet-page-form, body.neopets_pages-create #closet-page-form {
display: block; display: block;
} }
/* line 20, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 20, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #closet-page-frame-wrapper, body.closet_pages-create #closet-page-frame-wrapper { body.neopets_pages-new #closet-page-frame-wrapper, body.neopets_pages-create #closet-page-frame-wrapper {
float: left; float: left;
margin-right: 2%; margin-right: 2%;
width: 48%; width: 48%;
} }
/* line 25, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 25, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #closet-page-frame, body.closet_pages-create #closet-page-frame { body.neopets_pages-new #closet-page-frame, body.neopets_pages-create #closet-page-frame {
height: 19em; height: 19em;
width: 100%; width: 100%;
} }
/* line 29, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 29, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #closet-page-source, body.closet_pages-create #closet-page-source { body.neopets_pages-new #closet-page-source, body.neopets_pages-create #closet-page-source {
float: left; float: left;
width: 50%; width: 50%;
} }
/* line 33, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 33, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #closet-page-source label, body.closet_pages-create #closet-page-source label { body.neopets_pages-new #closet-page-source label, body.neopets_pages-create #closet-page-source label {
font-weight: bold; font-weight: bold;
} }
/* line 36, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 36, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new #closet-page-source textarea, body.closet_pages-create #closet-page-source textarea { body.neopets_pages-new #closet-page-source textarea, body.neopets_pages-create #closet-page-source textarea {
height: 19em; height: 19em;
} }
/* line 40, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 40, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new ol, body.closet_pages-create ol { body.neopets_pages-new ol, body.neopets_pages-create ol {
padding-left: 1em; padding-left: 1em;
} }
/* line 43, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 43, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new ol > li, body.closet_pages-create ol > li { body.neopets_pages-new ol > li, body.neopets_pages-create ol > li {
margin-bottom: 1em; margin-bottom: 1em;
} }
/* line 46, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 46, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new ol ul, body.closet_pages-create ol ul { body.neopets_pages-new ol ul, body.neopets_pages-create ol ul {
font-size: 85%; font-size: 85%;
margin-bottom: 1em; margin-bottom: 1em;
margin-top: 0; margin-top: 0;
padding-left: 1em; padding-left: 1em;
} }
/* line 53, ../../../app/stylesheets/closet_pages/_new.sass */ /* line 53, ../../../app/stylesheets/neopets_pages/_new.sass */
body.closet_pages-new ol p, body.closet_pages-create ol p { body.neopets_pages-new ol p, body.neopets_pages-create ol p {
margin: 0; margin: 0;
} }