Soleone-gamefaqs 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +27 -3
- data/lib/gamefaqs.rb +24 -1
- data/lib/gamefaqs/caching.rb +60 -5
- data/lib/gamefaqs/game.rb +12 -2
- data/lib/gamefaqs/list.rb +90 -52
- data/lib/gamefaqs/question.rb +18 -0
- data/lib/gamefaqs/random.rb +15 -3
- data/lib/gamefaqs/search.rb +1 -1
- data/test/gamefaqs_test.rb +42 -1
- metadata +3 -2
data/README.textile
CHANGED
@@ -57,24 +57,48 @@ h3. Reviews
|
|
57
57
|
</code>
|
58
58
|
</pre>
|
59
59
|
|
60
|
+
h3. Questions (and Answers)
|
61
|
+
|
62
|
+
<pre>
|
63
|
+
<code>
|
64
|
+
# Get an array with all user questions for a game
|
65
|
+
game.questions
|
66
|
+
|
67
|
+
question = game.questions.first
|
68
|
+
|
69
|
+
# Get the number of answers for this question
|
70
|
+
question.replies
|
71
|
+
# Get the status of the question (e.g. already answered or not)
|
72
|
+
question.status
|
73
|
+
# Get an array of all the answers for this question
|
74
|
+
question.answers
|
75
|
+
</code>
|
76
|
+
</pre>
|
77
|
+
|
60
78
|
h3. Examples for quick random quotes
|
61
79
|
|
62
80
|
<pre>
|
63
81
|
<code>
|
64
82
|
include GameFaqs
|
65
83
|
|
84
|
+
game = Game.find("super mario land", "game boy")
|
85
|
+
|
66
86
|
# The title of a random review for Super Mario Land
|
87
|
+
Random.review(game).title
|
88
|
+
# long version
|
67
89
|
Random.review("super mario land", "game boy").title
|
68
90
|
|
69
|
-
Random.one_line_review(
|
91
|
+
Random.one_line_review(game)
|
70
92
|
# => "Not Nintendo's (or my) best, but still a decent effort." - 6/10
|
71
93
|
|
72
|
-
Random.one_line_review(
|
94
|
+
Random.one_line_review(game, :detailed => true)
|
73
95
|
# => "Super Mario Land [Game Boy]: Not Nintendo's (or my) best, but still a decent effort." - 6/10
|
96
|
+
|
97
|
+
# The title of a random user question for a game
|
98
|
+
question = Random.question(game).title
|
74
99
|
</code>
|
75
100
|
</pre>
|
76
101
|
|
77
|
-
|
78
102
|
h2. Requirements
|
79
103
|
|
80
104
|
*Hpricot* (will be installed automatically by RubyGems as a dependency)
|
data/lib/gamefaqs.rb
CHANGED
@@ -6,13 +6,14 @@ require 'open-uri'
|
|
6
6
|
require 'date'
|
7
7
|
|
8
8
|
# load all source files
|
9
|
-
lib = %w[caching platform game search list review random]
|
9
|
+
lib = %w[caching platform game search list review question random]
|
10
10
|
lib.each { |file| require File.join(File.dirname(__FILE__), 'gamefaqs', file) }
|
11
11
|
|
12
12
|
|
13
13
|
module GameFaqs
|
14
14
|
BASE_URL = "http://www.gamefaqs.com"
|
15
15
|
SEARCH_URL = "#{BASE_URL}/search/index.html"
|
16
|
+
SITEMAP_URL = "http://sitemap.gamefaqs.com"
|
16
17
|
|
17
18
|
protected
|
18
19
|
def self.extract_id(url, with_html=true)
|
@@ -20,6 +21,11 @@ protected
|
|
20
21
|
$1
|
21
22
|
end
|
22
23
|
|
24
|
+
def self.extract_question_id(url)
|
25
|
+
url.match(/\/[\da-zA-Z]+\.html?qid=(\d+)$/)
|
26
|
+
$1
|
27
|
+
end
|
28
|
+
|
23
29
|
# 1. convert <br> to \n
|
24
30
|
# 2. convert <b> to * (textile)
|
25
31
|
# 3. convert <i> to _ (textile)
|
@@ -27,4 +33,21 @@ protected
|
|
27
33
|
def self.strip_html(string)
|
28
34
|
string.gsub(/<br ?\/?>/, "\n").gsub(/<b>(.+)<\/b>/i, "*\\1*").gsub(/<i>(.+)<\/i>/i, "_\\1_").gsub(/<\/?(\d|\w)+>/i, "")
|
29
35
|
end
|
36
|
+
|
37
|
+
# Extract a table with a certain header from a html document
|
38
|
+
# Yields 1) the Hpricot body element which usually contains a table
|
39
|
+
# And 2) the heading of the table as a String
|
40
|
+
def self.find_table(doc, header_regexp=/./, sub_path=nil)
|
41
|
+
doc.search("//div.head/h1") do |h1|
|
42
|
+
header = h1.inner_html
|
43
|
+
|
44
|
+
if header =~ header_regexp
|
45
|
+
body_path = "../../div.body#{'/'+sub_path if sub_path}"
|
46
|
+
h1.search(body_path) do |body|
|
47
|
+
yield body, header
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
30
53
|
end
|
data/lib/gamefaqs/caching.rb
CHANGED
@@ -1,16 +1,71 @@
|
|
1
1
|
module GameFaqs
|
2
2
|
module Caching
|
3
|
+
class Store < Hash
|
4
|
+
attr_reader :keys
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super(*args)
|
8
|
+
@keys = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, value)
|
12
|
+
@keys << key
|
13
|
+
clean!
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# retain only second half of cache if it gets to big
|
18
|
+
def clean!
|
19
|
+
if @keys.size > Cache.max_size
|
20
|
+
@keys = @keys[(Cache.max_size/2)...@keys.size]
|
21
|
+
reject! { |key, value| !@keys.include?(key) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
3
26
|
|
27
|
+
class Cache
|
28
|
+
STORE = Store.new
|
29
|
+
|
30
|
+
@@max_size = 100
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def max_size=(size)
|
34
|
+
@@max_size = size
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_size
|
38
|
+
@@max_size
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](key)
|
42
|
+
STORE[key]
|
43
|
+
end
|
44
|
+
alias :fetch :[]
|
45
|
+
|
46
|
+
def []=(key, value)
|
47
|
+
STORE[key] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
def size
|
51
|
+
STORE.size
|
52
|
+
end
|
53
|
+
|
54
|
+
def each(*args, &blk)
|
55
|
+
STORE.each(*args, &blk)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
4
60
|
protected
|
5
61
|
# perform the block only when the name isn't filled already
|
6
62
|
def cached_value(name, object=nil, force=false)
|
7
|
-
|
8
|
-
|
9
|
-
@@cache[name] = object
|
63
|
+
if force || Cache.fetch(name).nil?
|
64
|
+
Cache[name] = object
|
10
65
|
return_value = yield object
|
11
|
-
|
66
|
+
Cache[name] = return_value if object.nil?
|
12
67
|
end
|
13
|
-
|
68
|
+
Cache.fetch(name)
|
14
69
|
end
|
15
70
|
end
|
16
71
|
end
|
data/lib/gamefaqs/game.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module GameFaqs
|
2
2
|
class Game
|
3
|
-
|
3
|
+
|
4
|
+
GENRES = { "Action" => "54", "Action-Adventure" => "163", "Adventure" => "50", "Driving" => "47", "Miscellaneous" => "49", "Role-Playing" => "48", "Simulation" => "46", "Sports" => "43", "Strategy" => "45"}
|
5
|
+
|
4
6
|
attr_reader :name, :platform, :homepage, :faqs, :codes
|
5
7
|
|
6
8
|
def initialize(name, platform, id)
|
@@ -22,9 +24,17 @@ module GameFaqs
|
|
22
24
|
List.reviews(self, review_type)
|
23
25
|
end
|
24
26
|
|
27
|
+
def questions
|
28
|
+
List.questions(self)
|
29
|
+
end
|
30
|
+
|
25
31
|
def average_score(review_type=nil)
|
26
32
|
sum = reviews(review_type).map{|r| r.score_to_i}.inject{|memo, score| memo + score}
|
27
|
-
sum / reviews(review_type).size.to_f
|
33
|
+
sum ? (sum / reviews(review_type).size.to_f) : -1
|
34
|
+
end
|
35
|
+
|
36
|
+
def average_score_to_s
|
37
|
+
"%.2f - #{to_s}" % average_score
|
28
38
|
end
|
29
39
|
|
30
40
|
def self.find(game, platform)
|
data/lib/gamefaqs/list.rb
CHANGED
@@ -17,49 +17,57 @@ module GameFaqs
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
# find all games (very expensive)
|
20
|
+
# find all games (very expensive, takes nearly a minute, because of 27 html requests and resulting parsing)
|
21
21
|
def games(platform, refresh=false)
|
22
|
-
|
22
|
+
platform = Platform.find(platform) unless platform.is_a?(Platform)
|
23
|
+
cached_value("games-#{platform.to_s.downcase}", []) do |games|
|
23
24
|
letters = ('a'..'z').to_a << '0'
|
24
25
|
letters.each do |letter|
|
25
26
|
doc = Hpricot(open("#{platform.homepage}list_#{letter}.html"))
|
26
|
-
|
27
|
-
name = link.inner_html
|
28
|
-
games << Game.new(name, platform, GameFaqs.extract_id(link['href']))
|
29
|
-
end
|
27
|
+
insert_games_to_array(games, doc, platform, /Games by/)
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
32
|
+
def games_by_genre(platform, genre, refresh=false)
|
33
|
+
platform = Platform.find(platform) unless platform.is_a?(Platform)
|
34
|
+
cached_value("games_by_#{genre.downcase}-#{platform.to_s.downcase}", []) do |games|
|
35
|
+
doc = Hpricot(open("#{platform.homepage}cat_#{Game.GENRES[genre]}.html"))
|
36
|
+
insert_games_to_array(games, doc, platform, /Games by Category/)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def top_games(platform, refresh=false)
|
41
|
+
platform = Platform.find(platform) unless platform.is_a?(Platform)
|
42
|
+
cached_value("top_games-#{platform.to_s.downcase}", []) do |games|
|
43
|
+
doc = Hpricot(open(platform.homepage))
|
44
|
+
insert_games_to_array(games, doc, platform, /Top 10 Games/, "ul/li/a")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
34
48
|
def reviews(game, type=nil, refresh=false)
|
35
|
-
reviews = cached_value("reviews-#{game.to_s}", [], refresh) do |reviews|
|
49
|
+
reviews = cached_value("reviews-#{game.to_s.downcase}", [], refresh) do |reviews|
|
36
50
|
url = game.homepage.sub(/\/home\//, "/review/")
|
37
51
|
doc = Hpricot(open(url))
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
review =
|
44
|
-
review[:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
tr.search("td:eq(1)") do |td|
|
52
|
-
td.search("a") do |a|
|
53
|
-
review[:author] = a.inner_html.strip
|
54
|
-
end
|
55
|
-
end
|
56
|
-
tr.search("td:eq(2)") do |td|
|
57
|
-
review[:score] = td.inner_html.strip
|
58
|
-
end
|
59
|
-
review[:game] = game
|
60
|
-
reviews << Review.new(review)
|
52
|
+
GameFaqs.find_table(doc, /Reviews/, "table/tr") do |tr, header|
|
53
|
+
review = {}
|
54
|
+
review[:type] = Review.review_type(header)
|
55
|
+
tr.search("td:eq(0)") do |td|
|
56
|
+
td.search("a") do |a|
|
57
|
+
review[:id] = GameFaqs.extract_id(a['href'])
|
58
|
+
review[:title] = a.inner_html.strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
tr.search("td:eq(1)") do |td|
|
62
|
+
td.search("a") do |a|
|
63
|
+
review[:author] = a.inner_html.strip
|
61
64
|
end
|
62
65
|
end
|
66
|
+
tr.search("td:eq(2)") do |td|
|
67
|
+
review[:score] = td.inner_html.strip
|
68
|
+
end
|
69
|
+
review[:game] = game
|
70
|
+
reviews << Review.new(review)
|
63
71
|
end
|
64
72
|
end
|
65
73
|
if type
|
@@ -71,33 +79,53 @@ module GameFaqs
|
|
71
79
|
end
|
72
80
|
end
|
73
81
|
|
82
|
+
def questions(game, refresh=false)
|
83
|
+
questions = cached_value("questions-#{game.to_s.downcase}", [], refresh) do |questions|
|
84
|
+
url = game.homepage.sub(/\/home\//, "/qna/") << "?type=-1"
|
85
|
+
doc = Hpricot(open(url))
|
86
|
+
GameFaqs.find_table(doc, /Help$/, "table/tr") do |tr, header|
|
87
|
+
question = {}
|
88
|
+
question[:type] = header
|
89
|
+
tr.search("td:eq(0)") do |td|
|
90
|
+
td.search("a") do |a|
|
91
|
+
question[:id] = GameFaqs.extract_question_id(a['href'])
|
92
|
+
question[:title] = a.inner_html.strip
|
93
|
+
end
|
94
|
+
end
|
95
|
+
tr.search("td:eq(1)") do |td|
|
96
|
+
question[:status] = td.inner_html
|
97
|
+
end
|
98
|
+
tr.search("td:eq(2)") do |td|
|
99
|
+
question[:replies] = td.inner_html
|
100
|
+
end
|
101
|
+
questions << Question.new(game, question[:id], question)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
74
106
|
def faqs(game, type=nil, refresh=false)
|
75
|
-
faqs = cached_value("faqs-#{game.to_s}", [], refresh) do |faqs|
|
107
|
+
faqs = cached_value("faqs-#{game.to_s.downcase}", [], refresh) do |faqs|
|
76
108
|
url = game.homepage.sub(/\/home\//, "/game/")
|
77
109
|
doc = Hpricot(open(url))
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
td.search("a") do |a|
|
86
|
-
review[:id] = GameFaqs.extract_id(a['href'])
|
87
|
-
review[:title] = a.inner_html.strip
|
88
|
-
end
|
110
|
+
GameFaqs.find_table(doc, /./, "table/tr") do |tr, header|
|
111
|
+
faq = {}
|
112
|
+
faq[:type] = header
|
113
|
+
tr.search("td:eq(0)") do |td|
|
114
|
+
td.search("a") do |a|
|
115
|
+
review[:id] = GameFaqs.extract_id(a['href'])
|
116
|
+
review[:title] = a.inner_html.strip
|
89
117
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
end
|
95
|
-
tr.search("td:eq(2)") do |td|
|
96
|
-
review[:score] = td.inner_html.strip
|
118
|
+
end
|
119
|
+
tr.search("td:eq(1)") do |td|
|
120
|
+
td.search("a") do |a|
|
121
|
+
review[:author] = a.inner_html.strip
|
97
122
|
end
|
98
|
-
review[:game] = game
|
99
|
-
reviews << Review.new(review)
|
100
123
|
end
|
124
|
+
tr.search("td:eq(2)") do |td|
|
125
|
+
review[:score] = td.inner_html.strip
|
126
|
+
end
|
127
|
+
review[:game] = game
|
128
|
+
reviews << Review.new(review)
|
101
129
|
end
|
102
130
|
end
|
103
131
|
if type
|
@@ -118,7 +146,17 @@ module GameFaqs
|
|
118
146
|
end
|
119
147
|
end
|
120
148
|
end
|
121
|
-
|
149
|
+
|
150
|
+
private
|
151
|
+
# Will add all games found within the +doc+ to the specfied +array+
|
152
|
+
# Search in a table with the header +header_regexp+ and the specified +sub_path+
|
153
|
+
def insert_games_to_array(array, doc, platform, header_regexp, sub_path="table/tr/td:eq(0)/a")
|
154
|
+
GameFaqs.find_table(doc, header_regexp, sub_path) do |link, header|
|
155
|
+
name = link.inner_html
|
156
|
+
array << Game.new(name, platform, GameFaqs.extract_id(link['href']))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
122
160
|
end # class < self
|
123
161
|
end
|
124
162
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module GameFaqs
|
2
|
+
class Question
|
3
|
+
attr_reader :game, :id, :homepage, :title, :status, :replies, :type
|
4
|
+
|
5
|
+
def initialize(game, id, options={})
|
6
|
+
@game, @id = game, id
|
7
|
+
@homepage = @game.homepage.gsub(/home/, "qna") << "?id=#{@id}"
|
8
|
+
@title, @status, @replies, @type = options[:title], options[:status], options[:replies], options[:type]
|
9
|
+
end
|
10
|
+
|
11
|
+
def params(list=:all)
|
12
|
+
case list
|
13
|
+
when :all
|
14
|
+
"type=-1"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/gamefaqs/random.rb
CHANGED
@@ -3,14 +3,26 @@ module GameFaqs
|
|
3
3
|
extend self
|
4
4
|
|
5
5
|
# A review for a game
|
6
|
-
def review(game, platform)
|
6
|
+
def review(game, platform=game.platform)
|
7
7
|
game = Search.game(game, platform) if game.is_a?(String)
|
8
|
-
game and
|
8
|
+
game and random(game.reviews)
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
# Return the title and the score of a review
|
12
|
+
def one_line_review(game, platform=game.platform, options={:detailed => false})
|
12
13
|
rev = review(game, platform)
|
13
14
|
rev and "#{options[:detailed] ? (rev.game.to_s << ': ') : ''}\"#{rev.title}\" - #{rev.score}"
|
14
15
|
end
|
16
|
+
alias :opinion :one_line_review
|
17
|
+
|
18
|
+
def question(game, platform=game.platform)
|
19
|
+
game = Search.game(game, platform) if game.is_a?(String)
|
20
|
+
game and random(game.questions)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def random(array)
|
25
|
+
array[rand(array.size)]
|
26
|
+
end
|
15
27
|
end
|
16
28
|
end
|
data/lib/gamefaqs/search.rb
CHANGED
@@ -8,7 +8,7 @@ module GameFaqs
|
|
8
8
|
# Per default it returns nil when no game could be found, or if there are multiple matches
|
9
9
|
# Throws a detailed SearchError with help when no exact match is found (only when :whiny => true)
|
10
10
|
def game(game_name, platform, options={:refresh => false, :whiny => false})
|
11
|
-
cached_value("search-#{game_name}
|
11
|
+
cached_value("search-#{game_name.to_s.downcase} [#{platform.to_s.downcase}]", nil, options[:refresh]) do
|
12
12
|
platform = self.platform(platform) unless platform.is_a?(Platform)
|
13
13
|
games = []
|
14
14
|
doc = Hpricot(open("#{SEARCH_URL}#{add_params(game_name, platform)}"))
|
data/test/gamefaqs_test.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'test/unit'
|
2
|
-
require '../lib/gamefaqs'
|
2
|
+
require File.join(File.dirname(__FILE__), '../lib/gamefaqs')
|
3
3
|
|
4
4
|
class GameFaqsTest < Test::Unit::TestCase
|
5
5
|
include GameFaqs
|
@@ -35,4 +35,45 @@ class GameFaqsTest < Test::Unit::TestCase
|
|
35
35
|
assert @game.reviews.first.score[/(\d\d?)\/\d\d/]
|
36
36
|
assert_equal $1.to_i, @game.reviews.first.score_to_i
|
37
37
|
end
|
38
|
+
|
39
|
+
# takes about a minute!
|
40
|
+
|
41
|
+
#def test_list_all_games
|
42
|
+
# games = List.games("ds")
|
43
|
+
# assert games.size > 100
|
44
|
+
# assert games.first.is_a?(Game)
|
45
|
+
#end
|
46
|
+
|
47
|
+
def test_list_all_top_games
|
48
|
+
games = List.top_games("pc")
|
49
|
+
assert_equal 10, games.size
|
50
|
+
assert games.first.is_a?(Game)
|
51
|
+
end
|
52
|
+
|
53
|
+
def list_games_by_genre
|
54
|
+
games = List.games_by_genre("pc", "Action")
|
55
|
+
assert games.size > 100
|
56
|
+
assert games.first.is_a?(Game)
|
57
|
+
end
|
58
|
+
|
59
|
+
def list_questions_for_game
|
60
|
+
game = Game.find("fallout 3", "pc")
|
61
|
+
assert List.questions(game).size > 50
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_cache_resizing
|
65
|
+
Caching::Cache.max_size = 3
|
66
|
+
5.times { Game.find("fallout 3", "pc") }
|
67
|
+
puts Caching::Cache.size
|
68
|
+
assert Caching::Cache.size <= 3
|
69
|
+
Caching::Cache.max_size = 100
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_random_question
|
73
|
+
game = Game.find("fallout 3", "pc")
|
74
|
+
q = Random.question(game)
|
75
|
+
assert q.is_a?(Question)
|
76
|
+
puts q.title
|
77
|
+
end
|
78
|
+
|
38
79
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: Soleone-gamefaqs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dennis Theisen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-11-
|
12
|
+
date: 2008-11-18 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- lib/gamefaqs/list.rb
|
38
38
|
- lib/gamefaqs/platform.rb
|
39
39
|
- lib/gamefaqs/review.rb
|
40
|
+
- lib/gamefaqs/question.rb
|
40
41
|
- lib/gamefaqs/search.rb
|
41
42
|
- lib/gamefaqs/random.rb
|
42
43
|
- README.textile
|