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.
@@ -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("super mario land", "game boy")
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("super mario land", "game boy", :detailed => true)
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)
@@ -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
@@ -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
- @@cache ||= {}
8
- if force || @@cache[name].nil?
9
- @@cache[name] = object
63
+ if force || Cache.fetch(name).nil?
64
+ Cache[name] = object
10
65
  return_value = yield object
11
- @@cache[name] = return_value if object.nil?
66
+ Cache[name] = return_value if object.nil?
12
67
  end
13
- @@cache[name]
68
+ Cache.fetch(name)
14
69
  end
15
70
  end
16
71
  end
@@ -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)
@@ -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
- cached_value("games", []) do |games|
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
- doc.search(GAMES_PATH).each do |link|
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
- doc.search("//div.head/h1") do |h1|
39
- header = h1.inner_html
40
-
41
- if header =~ /Reviews/
42
- h1.search("../../div.body/table/tr") do |tr|
43
- review = {}
44
- review[:type] = Review.review_type(header)
45
- tr.search("td:eq(0)") do |td|
46
- td.search("a") do |a|
47
- review[:id] = GameFaqs.extract_id(a['href'])
48
- review[:title] = a.inner_html.strip
49
- end
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
- doc.search("//div.head/h1") do |h1|
79
- header = h1.inner_html
80
-
81
- h1.search("../../div.body/table/tr") do |tr|
82
- faq = {}
83
- faq[:type] = header
84
- tr.search("td:eq(0)") do |td|
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
- tr.search("td:eq(1)") do |td|
91
- td.search("a") do |a|
92
- review[:author] = a.inner_html.strip
93
- end
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
@@ -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 game.reviews[rand(game.reviews.size)]
8
+ game and random(game.reviews)
9
9
  end
10
10
 
11
- def one_line_review(game, platform, options={:detailed => false})
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
@@ -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}-#{platform}", nil, options[:refresh]) do
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)}"))
@@ -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
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-11 00:00:00 -08:00
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