gixen.rb 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env ruby
  2. require 'uri'
  3. require 'cgi'
  4. require 'net/https'
  5. require 'gixen_error'
  6. begin
  7. require 'to_query'
  8. rescue LoadError => err
  9. require 'active_support'
  10. require 'active_support/all'
  11. end
  12. class Gixen
  13. CORE_GIXEN_URL='https://www.gixen.com/api.php' #:nodoc:
  14. #:nodoc:
  15. LISTING_FORMAT = [:break,
  16. :itemid,
  17. :endtime,
  18. :maxbid,
  19. :status,
  20. :message,
  21. :title,
  22. :snipegroup,
  23. :quantity,
  24. :bidoffset]
  25. # Create a Gixen object for interacting with the user's Gixen
  26. # account, placing snipes, deleting snipes, and determining what
  27. # snipes have been set up.
  28. #
  29. # [+user+] an eBay username
  30. # [+pass+] an eBay password
  31. # [+validate_ssl+] true requests validation against the gixen cert file, false presumes the SSL is valid. Defaults to false.
  32. #
  33. # Gixen uses eBay authentication for its users, so it doesn't have
  34. # to have a different user/pass for its users.
  35. def initialize(user, pass, validate_ssl = false)
  36. @username = user
  37. @password = pass
  38. @validate_ssl = validate_ssl
  39. end
  40. private
  41. def gixen_url
  42. "#{CORE_GIXEN_URL}?username=#{@username}&password=#{@password}&notags=1"
  43. end
  44. def submit(params)
  45. url = "#{gixen_url}&#{params.to_query}"
  46. uri = URI.parse(url)
  47. http = Net::HTTP.new(uri.host, uri.port)
  48. http.use_ssl = true if uri.scheme == 'https' # enable SSL/TLS
  49. if @validate_ssl
  50. http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  51. http.ca_file = pem_file
  52. else
  53. http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  54. end
  55. http.get("#{uri.path}?#{uri.query}")
  56. end
  57. def pem_file
  58. File.expand_path(File.dirname(__FILE__) + "/gixen.pem")
  59. end
  60. def parse_response(resp)
  61. data = resp.body
  62. data.strip! if data
  63. if data =~ /^ERROR \(([0-9]+)\): (.*)$/
  64. error_code = $1
  65. error_text = $2
  66. raise GixenError.new(error_code.to_i, error_text)
  67. end
  68. data
  69. end
  70. def handle_snipes_list(body)
  71. body.split("\n").inject([]) do |accum, line|
  72. line.strip!
  73. hash = {}
  74. line.split("|#!#|").each_with_index do |entry, index|
  75. if index < LISTING_FORMAT.length
  76. hash[LISTING_FORMAT[index]] = entry
  77. else
  78. hash[index] = entry
  79. end
  80. end unless line =~ %r%^<br.*/>OK M(AIN|IRROR) LISTED$%
  81. hash.empty? ? accum : (accum << hash)
  82. end
  83. end
  84. def sourcify_hashes(hash_list, mirror = false)
  85. hash_list.each do |h|
  86. h[:mirror] = mirror
  87. end
  88. end
  89. public
  90. # Place a snipe on an +item+ (the auction item #) for +bid+ (string amount).
  91. # [+item+] the item number of the listing on eBay
  92. # [+bid+] a string amount of currency-neutral money, for example "23.50". The currency bid in will be the currency of the listing
  93. # [+options+] A collection of optional parameters for setting snipe meta-data.
  94. # Optional parameters include:
  95. # * <tt>:snipegroup => <i>group number</i></tt>, e.g. <tt>:snipegroup => 1</tt> (default: 0, no groups used)
  96. # * <tt>:quantity => <i>number</i></tt> (default: 1, single item auction) <b>[_obsolete_]</b>
  97. # * <tt>:bidoffset => <i>seconds before end</i></tt> (3, 6, 8, 10 or 15. Default value is 6)
  98. # * <tt>:bidoffsetmirror => <i>seconds before end</i></tt> (same as above, just for mirror server)
  99. #
  100. # @return [boolean] true if the snipe was successfully set, false if
  101. # something other than 'OK ADDED' came back, and throws a GixenError
  102. # if there is any server-side problem.
  103. def snipe(item, bid, options = {})
  104. response = submit({:itemid => item, :maxbid => bid}.merge(options))
  105. body = parse_response(response).strip
  106. !!(body =~ /^OK #{item} ADDED$/)
  107. end
  108. # Remove a snipe from an +item+ (the auction item #).
  109. #
  110. # @return [boolean] true if the snipe was successfully deleted, false if
  111. # something other than 'OK DELETED' came back, and throws a GixenError
  112. # if there is any server-side problem.
  113. def unsnipe(item)
  114. response = submit({:ditemid => item})
  115. body = parse_response(response).strip
  116. !!(body =~ /^OK #{item} DELETED$/)
  117. end
  118. # Lists all snipes set, skipped, or done on any Gixen server.
  119. #
  120. # @return [array of hashes] an array where each entry is a hash
  121. # containing all the info for a given snipe, an empty array if no
  122. # auctions are listed on either the main Gixen server or the Gixen
  123. # Mirror server.
  124. #
  125. # An additional field is added, :mirror, which is either true or
  126. # false, depending on if the item hash came from the mirror server
  127. # or not.
  128. #
  129. # It raises a GixenError if there is a server-side problem.
  130. def snipes
  131. sourcify_hashes(main_snipes) + sourcify_hashes(mirror_snipes, true)
  132. end
  133. # Lists all snipes currently set set, skipped, or done on Gixen's main server.
  134. #
  135. # @return [array of hashes] an array where each entry is a hash
  136. # containing all the info for a given snipe, an empty array if none
  137. # are listed on the main Gixen server. It raises a GixenError if
  138. # there is a server-side problem.
  139. def main_snipes
  140. response = submit({:listsnipesmain => 1})
  141. body = parse_response(response)
  142. handle_snipes_list(body)
  143. end
  144. # List all snipes currently set set, skipped, or done on Gixen's mirror server.
  145. #
  146. # @return [array of hashes] an array where each entry is a hash
  147. # containing all the info for a given snipe, an empty array if none
  148. # are listed on the Gixen Mirror server. It raises a GixenError if
  149. # there is a server-side problem.
  150. def mirror_snipes
  151. response = submit({:listsnipesmirror => 1})
  152. body = parse_response(response)
  153. handle_snipes_list(body)
  154. end
  155. # Normally the snipes that are completed are still listed when
  156. # retrieving snipes from the server; this method clears completed
  157. # listings, so the list of snipes is just active snipes.
  158. #
  159. # @return [boolean] true if the purge completed successfully. It
  160. # raises a GixenError if there is a server-side problem.
  161. def purge
  162. response = submit({:purgecompleted => 1})
  163. body = parse_response(response).strip
  164. !!(body =~ /^OK COMPLETEDPURGED$/)
  165. end
  166. end