Soleone-gamefaqs 0.0.4 → 0.0.5

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.
@@ -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