Class: MagentoMech

Inherits:
Object
  • Object
show all
Defined in:
lib/magento_remote/magento_mech.rb

Overview

The Mech Driver, interacting with a Magento shop page. Note that the Mech does not keep too much state, you have to care about login etc yourself.

Defined Under Namespace

Classes: ProductNotFoundError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_uri) ⇒ MagentoMech

Create Mech with base_uri



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/magento_remote/magento_mech.rb', line 29

def initialize base_uri
  @mech = Mechanize.new
  #@mech.user_agent = 'Mac Safari'
  #@mech.user_agent = ''
  @base_uri = base_uri
  @mech.agent.allowed_error_codes = [429]

  @mech.keep_alive = false
  @mech.open_timeout = 5
  @mech.read_timeout = 5
end

Instance Attribute Details

#base_uriObject

Returns the value of attribute base_uri.



12
13
14
# File 'lib/magento_remote/magento_mech.rb', line 12

def base_uri
  @base_uri
end

#passObject

Returns the value of attribute pass.



11
12
13
# File 'lib/magento_remote/magento_mech.rb', line 11

def pass
  @pass
end

#userObject

Returns the value of attribute user.



10
11
12
# File 'lib/magento_remote/magento_mech.rb', line 10

def user
  @user
end

Class Method Details

.from_config(conf) ⇒ Object

Create Mech from hash Argument conf

values :base_uri, :user, :pass.


21
22
23
24
25
26
# File 'lib/magento_remote/magento_mech.rb', line 21

def self.from_config(conf)
  client = MagentoMech.new(conf[:base_uri] || conf['base_uri'])
  client.user = conf[:user] || conf['user']
  client.pass = conf[:pass] || conf['pass']
  client
end

Instance Method Details

#add_to_cart(product_id, qty, form_token = nil) ⇒ Object

Put stuff in the cart. Use the form_token for magento >= 1.8 (or form token enforcing magento installations), otherwise leave it nil.

Returns true if succeeded, false otherwise.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/magento_remote/magento_mech.rb', line 87

def add_to_cart product_id, qty, form_token=nil
  fail "Empty obligatory parameter" if product_id.nil? || qty.to_i <= 0

  if !form_token.nil?
    return ajax_add_to_cart(product_id, qty, form_token)
  end
  url = URI.join @base_uri, "checkout/cart/add?product=#{product_id}&qty=#{qty}"

  # Check the returned page name
  result_page = @mech.get url

  # There are multiple reasons of failure:
  #   * product_id unknown
  #   * product out of stock
  #   * product does not exist (unhandled response (Mechanize::ResponseCodeError)
  #   )

  # There are multiple ways to detect failure:
  #   * contains a form with post action ending on product/#{product_id}/
  #   * title is different (stays at product page when failing)
  #   * no success msg div is shown.
  #   * body has a different class.
  #
  # Using the last of these options:
  # return result_page.search('.catalog-product-view').empty?

  return !result_page.search('.success-msg span').empty?
end

#add_to_cart!(product_id, qty, form_token = nil) ⇒ Object

Puts as many items of given product to cart as possible Returns number of items put to cart.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/magento_remote/magento_mech.rb', line 118

def add_to_cart! product_id, qty, form_token=nil
  # Try to be a bit clever and early find out whether article
  # is out of stock.
  if add_to_cart(product_id, qty, form_token)
    # to_i
    return qty
  end
  num_ordered = 0
  # Apparently not enough in stock!

  if qty.to_i > 4
    if !add_to_cart(product_id, 1, form_token)
      # out of stock
      return 0
    else
      num_ordered = 1
      qty = qty.to_i - 1
    end
  end
  while qty.to_i > 0 && !add_to_cart(product_id, qty, form_token)
    qty = qty.to_i - 1
  end
  qty.to_i + num_ordered
end

#ajax_add_to_cart(product_id, qty, form_token) ⇒ Object

See add_to_cart for more notes



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/magento_remote/magento_mech.rb', line 56

def ajax_add_to_cart product_id, qty, form_token
  if product_id.nil? || qty.to_i <= 0 || form_token.nil?
    fail "Empty obligatory parameter"
  end

  url = URI.join @base_uri, "checkout/cart/add/uenc/#{form_token}/"\
    "product/#{product_id}/?isajaxcart=true&groupmessage=1&minicart=1&ajaxlinks=1"
  result_page = @mech.post url,
    {product: product_id,
     qty: qty}

  result = JSON.parse result_page.content

  if result["outStock"]
    return false
  elsif result["message"].include? "error-msg"
    if result["message"].include? "Product not found!"
      raise ProductNotFoundError, "Product with id #{product_id} not found!"
    else
      return false
    end
  else
    return true
  end
end

#find_product(search_string) ⇒ Object

Search products. Arguments

search_string: sku, name or title, urlencoded for get request.

returns [[name1, product_id1, instock?1],[name2, p_id2…]…]

or nil if not found.


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/magento_remote/magento_mech.rb', line 177

def find_product search_string
  url = relative_url("/catalogsearch/result/index/?limit=all&q=#{search_string}")
  @mech.get url

  product_li = @mech.page.search('.equal-height .item')

  return nil if product_li.empty?

  products = product_li.map do |product|
    # Add to cart button is missing if out of stock.
    buttons = product.search("button")
    stock = buttons && !buttons[0].nil?

    # Find product ID from wishlist link.
    wishlist_link = product.search("ul li a")[0]
    wishlist_link.attributes['href'].value[/product\/(\d+)/]
    pid = $1

    # Find name from heading.
    name = product.search('h2')[0].text
    [name, pid, stock]
  end

  return products
end

#find_product_id_from(url) ⇒ Object



203
204
205
206
207
208
# File 'lib/magento_remote/magento_mech.rb', line 203

def find_product_id_from url
  page = @mech.get url
  r_pid = page.search(".//input[@name='product']")[0][:value]
  r_name = page.search(".product-name .roundall")
  [r_pid, r_name.text]
end

#get_cart_contentObject

Get the current carts contents Returns [[name, qty], [name2, qty2] … ]



151
152
153
154
155
156
157
158
# File 'lib/magento_remote/magento_mech.rb', line 151

def get_cart_content
  cart_page = @mech.get(URI.join @base_uri, "checkout/cart/")
  name_links = cart_page.search('td h2 a')
  names = name_links.map &:text
  quantities_inputs = cart_page.search('.qty')
  quantities = quantities_inputs.map {|n| n[:value]}
  names.zip quantities
end

#get_cart_content!Object

Login and get the current carts contents



144
145
146
147
# File 'lib/magento_remote/magento_mech.rb', line 144

def get_cart_content!
  
  get_cart_content
end

#last_order_productsObject

Get products of last order. Arguments returns [[product_name1, product_sku1, qty_ordered1],[name2, sku2…]…]

or empty list if not found.


269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/magento_remote/magento_mech.rb', line 269

def last_order_products
  orders_url = relative_url("/customer/account/")
  @mech.get orders_url
  order_url = @mech.page.search('.a-center a').first.attributes['href']
  @mech.get order_url
  @mech.page.search('tr.border').map do |tr|
    product_name = tr.children[1].children[0].content
    product_sku = tr.children[3].children[0].content
    product_qty = tr.children[7].children[1].content[/\d+/]
    [product_name, product_sku, product_qty]
  end
end

#last_ordersObject

Return list [date, volume, link, id, state] of last orders



255
256
257
258
259
260
261
262
263
# File 'lib/magento_remote/magento_mech.rb', line 255

def last_orders
  orders_url = relative_url("/customer/account/")
  @mech.get orders_url
  @mech.page.search('#my-orders-table tbody tr').map do |order_row|
    # We should remove the span labels
    row_columns = order_row.search("td")
    [row_columns[1].text, row_columns[3].text, row_columns[5].search("a").first[:href], row_columns[0].text[/\d+/], row_columns[4].text]
  end
end

#log_to!(file_or_logger) ⇒ Object

Log to given file (-like object) or use logger.



42
43
44
45
46
47
48
# File 'lib/magento_remote/magento_mech.rb', line 42

def log_to! file_or_logger
  if file_or_logger.is_a? Logger
    @mech.log = file_or_logger
  else
    @mech.log = Logger.new file_or_logger
  end
end

#loginObject

Login to webpage



51
52
53
# File 'lib/magento_remote/magento_mech.rb', line 51

def 
   @user, @pass
end

#login_with(username, password) ⇒ Object

Login with given credentials



161
162
163
164
165
166
167
168
169
170
# File 'lib/magento_remote/magento_mech.rb', line 161

def  username, password
   = @mech.get(URI.join @base_uri, "customer/account/login/")

  # Probably we could just send the POST directly.
  form = .form_with(:id => "login-form")
  form.action = URI.join @base_uri, "customer/account/loginPost/"
  form.fields.find{|f| f.name == 'login[username]'}.value = username
  form.fields.find{|f| f.name == 'login[password]'}.value = password
  @mech.submit(form)
end

#products_from_order(order_id) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
# File 'lib/magento_remote/magento_mech.rb', line 282

def products_from_order order_id
  order_url = relative_url("/sales/order/view/order_id/#{order_id}/")
  @mech.get order_url
  @mech.page.search('tr.border').map do |tr|
    product_name = tr.children[1].children[0].content
    product_sku  = tr.children[3].children[0].content
    product_qty  = tr.children[7].children[1].content[/\d+/] # "Ordered:
    refunded_qty = tr.children[7].children[1].content[/(?<=Refunded: )\d+/].to_i
    [product_name, product_sku, product_qty, refunded_qty]
  end
end

#scrape_products(start_pid, limit, sleep_time = 0) ⇒ Object

Search/scrape products. Arguments

limit: Maximum number of product_ids to check
start_pid: With which product id to start scraping
sleep_time: Time to sleep after each try

returns [[name1, product_id1, instock?1],[name2, p_id2…]…]

or nil if not found.

yielding would be nice



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/magento_remote/magento_mech.rb', line 218

def scrape_products start_pid, limit, sleep_time=0
  products = []
  limit.times do |idx|
    url = relative_url("/catalog/product/view/id/#{start_pid + idx + 1}")
    begin
      @mech.get url
    rescue
      # This is probably a 404
      sleep sleep_time
      next
    end

    if @mech.page.code == '429'
      # Too many requests! Sleep and try again,
      sleep 2

      @mech.get url rescue next
      if @mech.page.code == '429'
        raise "Remote Web Server reports too many requests"
      end
    end

    product_name = @mech.page.search('.product-name .roundall')[0].text
    wishlist_link = @mech.page.search(".link-wishlist")[0]
    wishlist_link.attributes['href'].value[/product\/(\d+)/]
    pid = $1
    products << [product_name, pid]
    if block_given?
      yield [product_name, pid]
    end
    sleep sleep_time
  end

  return products
end