rumble_bundle 0.1.1 → 1.0.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.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +6 -0
- data/bin/setup +1 -3
- data/config/environment.rb +4 -3
- data/{bin → exe}/rumble_bundle +0 -0
- data/lib/rumble_bundle/bundle.rb +5 -14
- data/lib/rumble_bundle/cli.rb +32 -13
- data/lib/rumble_bundle/product.rb +1 -2
- data/lib/rumble_bundle/scraper.rb +70 -66
- data/lib/rumble_bundle/tier.rb +3 -0
- data/lib/rumble_bundle/version.rb +1 -1
- data/rumble_bundle.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab912ac3d3ab1fa15544e0d5dde4421d1a9fb30c
|
4
|
+
data.tar.gz: '0479427cfff6e9e85a85d04917aaa9718f93c863'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9675a0ac23f75668bbf7ec75f61d13635b7d224d4cd240f93240c129f9c3ddb7033bf8eb7d1b6218c75d513a72f8f12bf157b95002d32718262b99918aaa1941
|
7
|
+
data.tar.gz: 54f374f52c02574af06e8e06ea3649d5dc82173f68e9b8e3213168d8677d6ca6a789c0210e24003a782607a2141c28bbd53cbfee66a7533efa37681a7fec2820
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -27,3 +27,9 @@ Then follow the on-screen prompts. :)
|
|
27
27
|
## Contributing
|
28
28
|
|
29
29
|
Bug reports and pull requests are welcome on GitHub at https://github.com/repromancer/rumble_bundle.
|
30
|
+
|
31
|
+
## License
|
32
|
+
|
33
|
+
This is free and unencumbered software released into the public domain.
|
34
|
+
|
35
|
+
For more information, please refer to the included UNLICENSE.md or <https://unlicense.org>
|
data/bin/setup
CHANGED
data/config/environment.rb
CHANGED
@@ -4,7 +4,8 @@ require 'nokogiri'
|
|
4
4
|
|
5
5
|
require 'rumble_bundle'
|
6
6
|
|
7
|
-
require_relative '../lib/rumble_bundle/cli'
|
8
|
-
require_relative '../lib/rumble_bundle/scraper'
|
9
7
|
require_relative '../lib/rumble_bundle/bundle'
|
10
|
-
require_relative '../lib/rumble_bundle/
|
8
|
+
require_relative '../lib/rumble_bundle/tier'
|
9
|
+
require_relative '../lib/rumble_bundle/product'
|
10
|
+
require_relative '../lib/rumble_bundle/scraper'
|
11
|
+
require_relative '../lib/rumble_bundle/cli'
|
data/{bin → exe}/rumble_bundle
RENAMED
File without changes
|
data/lib/rumble_bundle/bundle.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class RumbleBundle::Bundle
|
2
2
|
|
3
|
-
attr_accessor :name, :
|
3
|
+
attr_accessor :name, :url, :tiers, :charities, :total_msrp
|
4
4
|
|
5
5
|
@@all = []
|
6
6
|
|
@@ -8,22 +8,13 @@ class RumbleBundle::Bundle
|
|
8
8
|
@@all
|
9
9
|
end
|
10
10
|
|
11
|
-
def initialize
|
12
|
-
data_hash.each{|key, val| self.send("#{key}=", val)}
|
11
|
+
def initialize
|
13
12
|
self.class.all << self
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
#associate incoming products with Bundle
|
21
|
-
@products = array.tap do |products|
|
22
|
-
products.each do |product|
|
23
|
-
product.bundle = self
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
15
|
+
def products
|
16
|
+
# Leverage Bundle#tiers to expose a flat array of a Bundle's Products.
|
17
|
+
self.tiers.collect{|t| t.products}.flatten
|
27
18
|
end
|
28
19
|
|
29
20
|
end
|
data/lib/rumble_bundle/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
class RumbleBundle::CLI
|
2
2
|
|
3
|
+
# Scrape site and allow user to begin browsing.
|
3
4
|
def start
|
4
5
|
puts ""
|
5
6
|
puts "Fetching data from HumbleBundle.com..."
|
@@ -12,6 +13,7 @@ class RumbleBundle::CLI
|
|
12
13
|
end
|
13
14
|
|
14
15
|
|
16
|
+
|
15
17
|
def query
|
16
18
|
|
17
19
|
bundles = RumbleBundle::Bundle.all
|
@@ -21,22 +23,28 @@ class RumbleBundle::CLI
|
|
21
23
|
puts "Enter 'quit' to leave the program."
|
22
24
|
puts ""
|
23
25
|
|
26
|
+
# List Bundles in numbered order
|
24
27
|
bundles.each.with_index(1) do |bundle, i|
|
25
28
|
puts "[#{i}] #{bundle.name}"
|
26
29
|
end
|
27
30
|
|
31
|
+
# Get user input
|
28
32
|
puts ""
|
29
33
|
print " "
|
30
34
|
input = gets.strip.downcase
|
31
35
|
puts ""
|
32
36
|
|
37
|
+
# If input is a valid number, display the corresponding Bundle
|
33
38
|
if input.to_i.between?(1, bundles.length)
|
34
39
|
display_bundle(bundles[input.to_i - 1])
|
40
|
+
|
41
|
+
# If input is an invalid number, restart #query
|
35
42
|
elsif input.to_i < 0 || input.to_i > bundles.length
|
36
43
|
puts ""
|
37
44
|
puts "Sorry! Please enter a valid number or command."
|
38
45
|
query
|
39
46
|
|
47
|
+
# Check for 'help' or 'quit' commands, else pass input to #filter
|
40
48
|
else
|
41
49
|
case input
|
42
50
|
when 'help'
|
@@ -50,9 +58,14 @@ class RumbleBundle::CLI
|
|
50
58
|
|
51
59
|
end
|
52
60
|
|
61
|
+
|
62
|
+
|
53
63
|
def filter(input)
|
64
|
+
# Compare input to recognized tags
|
54
65
|
filters = %w[drm-free steam-key windows mac linux android]
|
55
66
|
valid = filters & input.split
|
67
|
+
|
68
|
+
# If input is valid, collect an array of all Products, remove any that do not match search tags, then display results in a formatted list
|
56
69
|
if valid.any?
|
57
70
|
filtered = RumbleBundle::Product.all.collect{|p| p}
|
58
71
|
valid.each do |filter|
|
@@ -73,8 +86,13 @@ class RumbleBundle::CLI
|
|
73
86
|
filtered.collect{|p| p.bundle }.uniq.each do |bundle|
|
74
87
|
puts "#{bundle.name} (#{bundle.url})"
|
75
88
|
puts ""
|
76
|
-
|
77
|
-
|
89
|
+
bundle.tiers.each do |tier|
|
90
|
+
if filtered.detect{|p| p.tier == tier}
|
91
|
+
puts " #{tier.description}", ""
|
92
|
+
filtered.each{|p| display_product(p) if p.tier == tier}
|
93
|
+
puts ""
|
94
|
+
end
|
95
|
+
end
|
78
96
|
end
|
79
97
|
puts "---------------------------------------------------------------"
|
80
98
|
else
|
@@ -95,25 +113,24 @@ class RumbleBundle::CLI
|
|
95
113
|
|
96
114
|
end
|
97
115
|
|
116
|
+
|
117
|
+
|
118
|
+
# Display formatted information for given Bundle
|
98
119
|
def display_bundle(bundle)
|
99
120
|
puts "---------------------------------------------------------------"
|
100
121
|
puts "#{bundle.name} (#{bundle.url})"
|
101
122
|
puts "#{bundle.total_msrp}!"
|
102
123
|
puts ""
|
103
|
-
puts "Supports:"
|
124
|
+
puts " Supports:"
|
104
125
|
bundle.charities.each do |c|
|
105
|
-
print "
|
126
|
+
print " #{c}"
|
106
127
|
puts ","
|
107
128
|
end
|
108
|
-
puts "
|
129
|
+
puts " or a Charity of Your Choice"
|
109
130
|
puts "", ""
|
110
131
|
bundle.tiers.each do |tier|
|
111
|
-
puts tier, ""
|
112
|
-
|
113
|
-
if product.tier == tier
|
114
|
-
display_product(product)
|
115
|
-
end
|
116
|
-
end
|
132
|
+
puts " #{tier.description}", ""
|
133
|
+
tier.products.each{|p| display_product(p)}
|
117
134
|
puts ""
|
118
135
|
end
|
119
136
|
puts "---------------------------------------------------------------"
|
@@ -122,15 +139,16 @@ class RumbleBundle::CLI
|
|
122
139
|
end
|
123
140
|
|
124
141
|
def display_product(product)
|
125
|
-
print "
|
142
|
+
print " #{product.name}"
|
126
143
|
print " (#{product.platforms.join(", ")})" if product.platforms.any?
|
127
144
|
print " (DRM-Free!)" if product.drm_free
|
128
145
|
print " (w/Steam Key!)" if product.steam_key
|
129
146
|
puts ""
|
130
|
-
puts "
|
147
|
+
puts " #{product.subtitle}" if product.subtitle
|
131
148
|
puts ""
|
132
149
|
end
|
133
150
|
|
151
|
+
# Display help
|
134
152
|
def help
|
135
153
|
puts <<~HEREDOC
|
136
154
|
---------------------------------------------------------------
|
@@ -153,6 +171,7 @@ class RumbleBundle::CLI
|
|
153
171
|
query
|
154
172
|
end
|
155
173
|
|
174
|
+
# Quit
|
156
175
|
def quit
|
157
176
|
exit
|
158
177
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class RumbleBundle::Scraper
|
2
2
|
|
3
3
|
def initialize
|
4
|
+
# Set URLs used as starting points for scraping
|
4
5
|
@base_url = 'https://www.humblebundle.com'
|
5
6
|
@main_pages = [
|
6
7
|
'https://www.humblebundle.com/',
|
@@ -11,15 +12,18 @@ class RumbleBundle::Scraper
|
|
11
12
|
|
12
13
|
|
13
14
|
def crawl_site
|
14
|
-
|
15
|
+
# For each main page
|
15
16
|
@main_pages.each do |page|
|
17
|
+
# scrape the bundle from that page
|
16
18
|
first_page = Nokogiri::HTML(open(page))
|
17
19
|
scrape_bundle(first_page, page)
|
18
20
|
|
21
|
+
# and if there are any sub-pages
|
19
22
|
if first_page.at_css(".js-highlight.subtab-button")
|
20
23
|
tab_bar = first_page.css(".js-highlight.subtab-button")
|
21
24
|
links = tab_bar.collect{|a| @base_url + a.attr("href") }
|
22
25
|
|
26
|
+
# scrape them for bundles too
|
23
27
|
links.each do |link|
|
24
28
|
scrape_bundle(Nokogiri::HTML(open(link)), link)
|
25
29
|
end
|
@@ -32,96 +36,96 @@ class RumbleBundle::Scraper
|
|
32
36
|
|
33
37
|
def scrape_bundle(html, url)
|
34
38
|
|
35
|
-
|
36
|
-
'name' => '',
|
37
|
-
'tiers' => [],
|
38
|
-
'products' => [],
|
39
|
-
'charities' => [],
|
40
|
-
'total_msrp' => '',
|
41
|
-
'url' => url
|
42
|
-
}
|
39
|
+
RumbleBundle::Bundle.new.tap do |bundle|
|
43
40
|
|
44
|
-
|
41
|
+
bundle.url = url
|
45
42
|
|
46
|
-
|
43
|
+
# Scrape Bundle metadata
|
47
44
|
|
48
|
-
|
49
|
-
html.css(".main-content-row").each do |tier|
|
45
|
+
bundle.name = html.css("title").text.chomp("(pay what you want and help charity)").strip
|
50
46
|
|
51
|
-
|
52
|
-
tier_name = tier.css(".dd-header-headline").text.strip
|
53
|
-
if tier_name.downcase.include?("charity")
|
54
|
-
break
|
55
|
-
end
|
47
|
+
bundle.charities = html.css(".charity-image-wrapper img").collect{|img| img.attr("alt")}
|
56
48
|
|
57
|
-
bundle
|
49
|
+
bundle.total_msrp = html.css('.hr-tagline-text').detect{|e| e.text.include?("worth")}.text.strip
|
58
50
|
|
59
|
-
#and instantiate products from tier
|
60
|
-
tier.css(".game-boxes").each do |box|
|
61
|
-
scrape_product(box, tier_name).tap do |product|
|
62
|
-
bundle['products'] << product
|
63
|
-
end
|
64
|
-
end
|
65
51
|
|
66
|
-
|
52
|
+
bundle.tiers = Array.new.tap do |bundle_tiers|
|
53
|
+
|
54
|
+
# For each 'main content row' on the page, instantiate a Tier with its Products and add that Tier to the Bundle's @tiers array
|
55
|
+
html.css(".main-content-row").each do |row|
|
56
|
+
|
57
|
+
row_title = row.css(".dd-header-headline").text.strip
|
67
58
|
|
68
|
-
|
59
|
+
# Stop iterating if scraper has reached a row which is not a Tier
|
60
|
+
if row_title.downcase.include?("charity")
|
61
|
+
break
|
62
|
+
end
|
69
63
|
|
70
|
-
|
64
|
+
bundle_tiers << RumbleBundle::Tier.new.tap do |tier|
|
65
|
+
tier.description = row_title
|
66
|
+
tier.bundle = bundle
|
67
|
+
tier.products = Array.new.tap do |tier_products|
|
68
|
+
row.css(".game-boxes").each do |box|
|
69
|
+
scrape_product(box, bundle, tier).tap do |product|
|
70
|
+
tier_products << product
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
71
75
|
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
72
81
|
end
|
73
82
|
|
74
83
|
|
84
|
+
# Self-explanatory
|
85
|
+
def scrape_product(box, bundle, tier)
|
75
86
|
|
76
|
-
|
87
|
+
RumbleBundle::Product.new.tap do |product|
|
77
88
|
|
78
|
-
|
79
|
-
|
80
|
-
'subtitle' => '',
|
81
|
-
'bundle' => '',
|
82
|
-
'tier' => '',
|
83
|
-
'platforms' => [],
|
84
|
-
'drm_free' => nil,
|
85
|
-
'steam_key' => nil
|
86
|
-
}
|
89
|
+
product.bundle = bundle
|
90
|
+
product.tier = tier
|
87
91
|
|
88
|
-
|
89
|
-
product['tier'] = tier
|
90
|
-
product['subtitle'] = if box.at_css(".subtitle")
|
91
|
-
box.css(".subtitle .callout-msrp").remove
|
92
|
-
if box.css(".subtitle").text.strip != ""
|
93
|
-
box.css(".subtitle").text.strip
|
94
|
-
else
|
95
|
-
nil
|
96
|
-
end
|
97
|
-
end
|
92
|
+
product.name = box.css(".dd-image-box-caption").text.strip
|
98
93
|
|
99
|
-
|
100
|
-
|
101
|
-
|
94
|
+
product.subtitle = if box.at_css(".subtitle")
|
95
|
+
box.css(".subtitle .callout-msrp").remove
|
96
|
+
if box.css(".subtitle").text.strip != ""
|
97
|
+
box.css(".subtitle").text.strip
|
98
|
+
else
|
99
|
+
nil
|
100
|
+
end
|
102
101
|
end
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
product.platforms = Array.new.tap do |platforms|
|
104
|
+
if box.at_css(".dd-availability-icon > i.hb-android")
|
105
|
+
platforms << 'Android'
|
106
|
+
end
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
if box.at_css(".dd-availability-icon > i.hb-linux")
|
109
|
+
platforms << 'Linux'
|
110
|
+
end
|
111
111
|
|
112
|
-
|
113
|
-
|
114
|
-
|
112
|
+
if box.at_css(".dd-availability-icon > i.hb-windows")
|
113
|
+
platforms << 'Windows'
|
114
|
+
end
|
115
115
|
|
116
|
-
|
116
|
+
if box.at_css(".dd-availability-icon > i.hb-osx")
|
117
|
+
platforms << 'Mac'
|
118
|
+
end
|
117
119
|
|
118
|
-
|
119
|
-
|
120
|
+
end
|
121
|
+
|
122
|
+
product.drm_free = box.at_css(".dd-availability-icon > i.hb-drmfree") ?
|
123
|
+
true : false
|
120
124
|
|
121
|
-
|
122
|
-
|
125
|
+
product.steam_key = box.at_css(".dd-availability-icon > i.hb-steam") ?
|
126
|
+
true : false
|
123
127
|
|
124
|
-
|
128
|
+
end
|
125
129
|
|
126
130
|
end
|
127
131
|
|
data/rumble_bundle.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rumble_bundle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Noah Summers
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -88,14 +88,15 @@ files:
|
|
88
88
|
- Rakefile
|
89
89
|
- UNLICENSE.md
|
90
90
|
- bin/console
|
91
|
-
- bin/rumble_bundle
|
92
91
|
- bin/setup
|
93
92
|
- config/environment.rb
|
93
|
+
- exe/rumble_bundle
|
94
94
|
- lib/rumble_bundle.rb
|
95
95
|
- lib/rumble_bundle/bundle.rb
|
96
96
|
- lib/rumble_bundle/cli.rb
|
97
97
|
- lib/rumble_bundle/product.rb
|
98
98
|
- lib/rumble_bundle/scraper.rb
|
99
|
+
- lib/rumble_bundle/tier.rb
|
99
100
|
- lib/rumble_bundle/version.rb
|
100
101
|
- rumble_bundle.gemspec
|
101
102
|
- spec.md
|