auto-bidder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.markdown +29 -0
- data/bin/auto-bidder +4 -0
- data/lib/auto_bidder.rb +115 -0
- data/lib/auto_bidder/auction_interface.rb +112 -0
- data/lib/auto_bidder/bigdeal.rb +53 -0
- data/lib/auto_bidder/browser_interface.rb +26 -0
- data/spec/auto_bidder_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +78 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 hoverlover
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# AutoBidder: Win without trying
|
2
|
+
|
3
|
+
This library places bids on online auction sites for you automatically while you do other important things, like walking your dog.
|
4
|
+
|
5
|
+
## Installing
|
6
|
+
|
7
|
+
gem install auto-bidder
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
AutoBidder is intended to be ran from the command line:
|
12
|
+
|
13
|
+
Chad-Boyds-iMac:~ cboyd$ auto-bidder --help
|
14
|
+
Usage: auto-bidder [options] URL
|
15
|
+
-v, --verbose Be more verbose
|
16
|
+
-x, --max-price PRICE Maximum price you are willing to pay for the auction item
|
17
|
+
-n, --min-price [PRICE] Minimum price the auction item must reach before bidding starts
|
18
|
+
-c, --bid-cost COST How much each bid actually costs you
|
19
|
+
-u, --username USER Username of your account for the auction site
|
20
|
+
-s, --bid-seconds [SECONDS] How many seconds remaining on the clock before a bid is placed
|
21
|
+
-h, --help Display this screen
|
22
|
+
|
23
|
+
It is a gem, though. So I suppose you could use it within your own project.
|
24
|
+
|
25
|
+
AutoBidder works by utilizing [Watir](http://watir.com), an open-source library for automating web browsers. The AutoBidder library will launch a Safari window and connect to the URL you supply on the command line.
|
26
|
+
|
27
|
+
## Notes
|
28
|
+
|
29
|
+
**Watir** supports IE, Firefox, Safari and Chrome. AutoBidder uses Safari only, however. Feel free to fork this project and implement any or all of the other browsers if you wish.
|
data/bin/auto-bidder
ADDED
data/lib/auto_bidder.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'safariwatir'
|
2
|
+
require 'optparse'
|
3
|
+
require 'auto_bidder/auction_interface'
|
4
|
+
require 'auto_bidder/bigdeal'
|
5
|
+
require 'auto_bidder/browser_interface'
|
6
|
+
require 'logger'
|
7
|
+
require 'uri'
|
8
|
+
|
9
|
+
class AutoBidder
|
10
|
+
def initialize(url, options)
|
11
|
+
@num_bids = 0
|
12
|
+
|
13
|
+
# Glean the TLD name from the host and use it to create the auction interface
|
14
|
+
host = URI.parse(url).host
|
15
|
+
begin
|
16
|
+
@auction_interface = Module.const_get(host.split(".")[-2].capitalize).new(url, options)
|
17
|
+
rescue NameError
|
18
|
+
$log.fatal "Auction site #{host} not supported"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
@auction_interface.connect_to_auction
|
24
|
+
@auction_interface.start_monitoring
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_command_line
|
29
|
+
# This hash will hold all of the options
|
30
|
+
# parsed from the command-line by
|
31
|
+
# OptionParser.
|
32
|
+
options = {}
|
33
|
+
|
34
|
+
optparse = OptionParser.new do|opts|
|
35
|
+
# Set a banner, displayed at the top
|
36
|
+
# of the help screen.
|
37
|
+
opts.banner = "Usage: auto-bidder [options] URL"
|
38
|
+
|
39
|
+
# Define the options, and what they do
|
40
|
+
options[:verbose] = false
|
41
|
+
opts.on('-v', '--verbose', 'Be more verbose') do
|
42
|
+
options[:verbose] = true
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-x", "--max-price PRICE", Float, 'Maximum price you are willing to pay for the auction item') do |max_price|
|
46
|
+
options[:max_price] = max_price
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-n", "--min-price [PRICE]", Float, 'Minimum price the auction item must reach before bidding starts') do |min_price|
|
50
|
+
options[:min_price] = min_price || 0.0
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-c", "--bid-cost COST", Float, 'How much each bid actually costs you') do |bid_cost|
|
54
|
+
options[:bid_cost] = bid_cost
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on("-u", "--username USER", 'Username of your account for the auction site') do |username|
|
58
|
+
options[:username] = username
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on("-s", "--bid-seconds [SECONDS]", Integer, 'How many seconds remaining on the clock before a bid is placed') do |bid_secs|
|
62
|
+
options[:bid_secs] = bid_secs || 1
|
63
|
+
end
|
64
|
+
|
65
|
+
# This displays the help screen, all programs are
|
66
|
+
# assumed to have this option.
|
67
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
68
|
+
puts opts
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse the command-line. Remember there are two forms
|
74
|
+
# of the parse method. The 'parse' method simply parses
|
75
|
+
# ARGV, while the 'parse!' method parses ARGV and removes
|
76
|
+
# any options found there, as well as any parameters for
|
77
|
+
# the options. What's left is the list of files to resize.
|
78
|
+
optparse.parse! ARGV
|
79
|
+
|
80
|
+
ensure_option_is_present options, :max_price, optparse
|
81
|
+
ensure_option_is_present options, :bid_cost, optparse
|
82
|
+
ensure_option_is_present options, :username, optparse
|
83
|
+
|
84
|
+
url = ARGV[0]
|
85
|
+
unless url
|
86
|
+
puts "URL must be given"
|
87
|
+
puts optparse
|
88
|
+
exit 1
|
89
|
+
end
|
90
|
+
|
91
|
+
ARGV.clear
|
92
|
+
|
93
|
+
if options[:username]
|
94
|
+
print "Auction site password:"
|
95
|
+
options[:password] = gets.chomp
|
96
|
+
end
|
97
|
+
|
98
|
+
$log = Logger.new(STDOUT)
|
99
|
+
$log.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
|
100
|
+
|
101
|
+
$log.debug "Verbose logging turned on" if $log.debug?
|
102
|
+
|
103
|
+
[url, options]
|
104
|
+
end
|
105
|
+
|
106
|
+
def ensure_option_is_present(options, name, parser)
|
107
|
+
if !options.has_key? name
|
108
|
+
puts "Option #{name} is required"
|
109
|
+
puts parser
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
url, options = parse_command_line
|
115
|
+
AutoBidder.new(url, options).start
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module AuctionInterface
|
2
|
+
|
3
|
+
def start_monitoring
|
4
|
+
while true do
|
5
|
+
reload_if_necessary
|
6
|
+
|
7
|
+
@hours = hours
|
8
|
+
@mins = mins
|
9
|
+
@secs = secs
|
10
|
+
print_stats
|
11
|
+
|
12
|
+
if @hours == 0 && @mins == 0 && @secs == @bid_secs && ok_to_bid
|
13
|
+
place_bid
|
14
|
+
sleep 10
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# --------------- #
|
20
|
+
# Private methods #
|
21
|
+
# --------------- #
|
22
|
+
def ok_to_bid
|
23
|
+
current_price = get_current_price
|
24
|
+
current_price >= @min_price && ((@num_bids + 1) * @bid_cost + current_price) <= @max_price
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload_if_necessary
|
28
|
+
# Reload every 8-10 minutes so the website doesn't think we've gone idle. If @secs is 5 or less
|
29
|
+
# put off the reload so we don't accidentally miss a bid opportunity.
|
30
|
+
if (Time.now.to_i - @last_reload) / 60 >= @next_reload_in && @secs > 10
|
31
|
+
@browser.reload
|
32
|
+
recalculate_next_reload_time
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def hours
|
37
|
+
begin
|
38
|
+
get_hours
|
39
|
+
rescue
|
40
|
+
$log.error "Could not find hours element"
|
41
|
+
@browser.reload
|
42
|
+
-1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def mins
|
47
|
+
begin
|
48
|
+
get_mins
|
49
|
+
rescue
|
50
|
+
$log.error "Could not find minutes element"
|
51
|
+
@browser.reload
|
52
|
+
-1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def secs
|
57
|
+
begin
|
58
|
+
get_secs
|
59
|
+
rescue
|
60
|
+
$log.info "Could not find seconds element"
|
61
|
+
@browser.reload
|
62
|
+
-1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def recalculate_next_reload_time
|
67
|
+
@next_reload_in = (rand(3) + 8)
|
68
|
+
end
|
69
|
+
|
70
|
+
def connect_to_auction
|
71
|
+
sign_in unless signed_in?
|
72
|
+
|
73
|
+
@last_reload = Time.now.to_i
|
74
|
+
recalculate_next_reload_time
|
75
|
+
end
|
76
|
+
|
77
|
+
def place_bid
|
78
|
+
$log.info <<-BIDDING
|
79
|
+
\n=======================================
|
80
|
+
PLACING BID
|
81
|
+
#{Time.now}
|
82
|
+
=======================================
|
83
|
+
BIDDING
|
84
|
+
|
85
|
+
begin
|
86
|
+
execute_bid
|
87
|
+
@num_bids += 1
|
88
|
+
|
89
|
+
# The server knows we're still here because we just placed a bid, so reset the next reload time.
|
90
|
+
recalculate_next_reload_time
|
91
|
+
rescue
|
92
|
+
$log.error "Error while placing bid!"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def print_stats
|
97
|
+
@last_debug ||= Time.now.to_i
|
98
|
+
|
99
|
+
# print debug every 10 seconds
|
100
|
+
if Time.now.to_i - @last_debug > 10
|
101
|
+
begin
|
102
|
+
$log.debug %(#{Time.now.strftime "%m/%d/%Y %H:%M:%S"} - #{"%02d" % @hours}:#{"%02d" % @mins}:#{"%02d" % @secs}, #{get_formatted_price})
|
103
|
+
rescue => e
|
104
|
+
$log.error "Could not print debug statement\n#{e.backtrace.join("\n")}"
|
105
|
+
end
|
106
|
+
@last_debug = Time.now.to_i
|
107
|
+
end
|
108
|
+
|
109
|
+
sleep 0.01
|
110
|
+
end
|
111
|
+
private :ok_to_bid, :reload_if_necessary, :hours, :mins, :secs, :recalculate_next_reload_time, :place_bid, :print_stats
|
112
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Bigdeal
|
2
|
+
include AuctionInterface
|
3
|
+
|
4
|
+
def initialize(url, options)
|
5
|
+
@auction_url = url
|
6
|
+
@auction_id = @auction_url.match(/([\d]+\Z)/)[0]
|
7
|
+
@browser = BrowserInterface.new @auction_url
|
8
|
+
|
9
|
+
@max_price = options[:max_price]
|
10
|
+
@min_price = options[:min_price]
|
11
|
+
@bid_cost = options[:bid_cost]
|
12
|
+
@bid_secs = options[:bid_secs]
|
13
|
+
@username = options[:username]
|
14
|
+
@password = options[:password]
|
15
|
+
|
16
|
+
@num_bids = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_current_price
|
20
|
+
@browser.span(:id, "#{@auction_id}-price").text.delete('$').to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
def signed_in?
|
24
|
+
@browser.link(:text, /Logout/).exists?
|
25
|
+
end
|
26
|
+
|
27
|
+
def sign_in
|
28
|
+
@browser.link(:text, /Login/).click
|
29
|
+
@browser.text_field(:id, "userNameOrEmail").set(@username)
|
30
|
+
@browser.text_field(:id, "password").set(@password)
|
31
|
+
@browser.form(:action, /login/).submit
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_hours
|
35
|
+
@browser.span(:id, "#{@auction_id}-hours_remaining").text.to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_mins
|
39
|
+
@browser.span(:id, "#{@auction_id}-minutes_remaining").text.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_secs
|
43
|
+
@browser.span(:id, "#{@auction_id}-seconds_remaining").text.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute_bid
|
47
|
+
@browser.div(:id, "#{@auction_id}-bid_button").click
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_formatted_price
|
51
|
+
@browser.span(:id, @auction_id + "-price").text
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class BrowserInterface < Watir::Safari
|
2
|
+
|
3
|
+
def initialize(url)
|
4
|
+
super()
|
5
|
+
@url = url
|
6
|
+
|
7
|
+
begin
|
8
|
+
goto @url
|
9
|
+
rescue
|
10
|
+
$log.error "Failed to load website. Will retry."
|
11
|
+
retry
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def reload
|
16
|
+
$log.debug "reloading ..."
|
17
|
+
begin
|
18
|
+
goto @url
|
19
|
+
@last_reload = Time.now.to_i
|
20
|
+
sleep 5 # give the browser time to finish loading before continuing
|
21
|
+
rescue
|
22
|
+
$log.error "Failed to reload. Will retry."
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: auto-bidder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- hoverlover
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-09 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: gem for automatically placing bids on auction sites
|
23
|
+
email: hoverlover@gmail.com
|
24
|
+
executables:
|
25
|
+
- auto-bidder
|
26
|
+
- auto-bidder
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files:
|
30
|
+
- LICENSE
|
31
|
+
- README.markdown
|
32
|
+
files:
|
33
|
+
- bin/auto-bidder
|
34
|
+
- lib/auto_bidder.rb
|
35
|
+
- lib/auto_bidder/auction_interface.rb
|
36
|
+
- lib/auto_bidder/bigdeal.rb
|
37
|
+
- lib/auto_bidder/browser_interface.rb
|
38
|
+
- LICENSE
|
39
|
+
- README.markdown
|
40
|
+
- spec/auto_bidder_spec.rb
|
41
|
+
- spec/spec_helper.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/hoverlover/auto-bidder
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options:
|
48
|
+
- --charset=UTF-8
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.7
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: gem for automatically placing bids on auction sites
|
76
|
+
test_files:
|
77
|
+
- spec/auto_bidder_spec.rb
|
78
|
+
- spec/spec_helper.rb
|