Soleone-gamefaqs 0.0.2

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.
@@ -0,0 +1,65 @@
1
+ h1. GameFAQs Library
2
+
3
+ Access information about all games (any platform) from GameFAQs.
4
+
5
+ You can search for games by title and platform, and then view _Reviews_, _FAQs_, _Cheats_ for it.
6
+
7
+ h2. Installation
8
+
9
+ @gem install soleone-gamefaqs --source=http://gems.github.com@
10
+
11
+ h2. Usage
12
+
13
+ <pre>
14
+ <code>
15
+ require 'gamefaqs'
16
+
17
+ # and for convenience:
18
+ include GameFaqs
19
+ </code>
20
+ </pre>
21
+
22
+ h2. Examples
23
+
24
+ h3. Find games and platforms
25
+
26
+ <pre>
27
+ <code>
28
+ # Search for a game containing two words on the Nintendo DS
29
+ game = GameFaqs::Search.game("Castlevania Ecclesia", "DS")
30
+
31
+ # You can also search starting from the platform
32
+ snes = GameFaqs::Platform.find("snes")
33
+ game = snes.find("super mario land")
34
+ </code>
35
+ </pre>
36
+
37
+ h3. Reviews
38
+
39
+ <pre>
40
+ <code>
41
+ # Get the average score from all reviews
42
+ game.average_score
43
+ # Get the average score from only detailed reviews (there are :detailed, :full and :quick)
44
+ game.average_score(:detailed)
45
+
46
+ # Get all reviews for this game
47
+ reviews = game.reviews
48
+ # Get only quick reviews for this game
49
+ reviews = game.reviews(:quick)
50
+
51
+ # Get the first review in the list
52
+ review = reviews.first
53
+
54
+ # Score in the format 9/10
55
+ review.score
56
+
57
+ # Get the full text of the review (original html stripped/converted)
58
+ review.text
59
+
60
+ # Other information
61
+ review.title
62
+ review.created_at
63
+ review.author
64
+ </code>
65
+ </pre>
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ # gems
3
+ require 'hpricot'
4
+
5
+ require 'open-uri'
6
+ require 'date'
7
+
8
+ # load all source files
9
+ lib = %w[caching platform game search list review]
10
+ lib.each { |file| require File.join(File.dirname(__FILE__), 'gamefaqs', file) }
11
+
12
+
13
+ module GameFaqs
14
+ BASE_URL = "http://www.gamefaqs.com"
15
+ SEARCH_URL = "#{BASE_URL}/search/index.html"
16
+
17
+ protected
18
+ def self.extract_id(url, with_html=true)
19
+ url.match(/\/([\da-zA-Z]+)#{'\.html' if with_html}$/)
20
+ $1
21
+ end
22
+
23
+ # 1. convert <br> to \n
24
+ # 2. convert <b> to * (textile)
25
+ # 3. convert <i> to _ (textile)
26
+ # 4. strip all other tags
27
+ def self.strip_html(string)
28
+ string.gsub(/<br ?\/?>/, "\n").gsub(/<b>(.+)<\/b>/i, "*\\1*").gsub(/<i>(.+)<\/i>/i, "_\\1_").gsub(/<\/?(\d|\w)+>/i, "")
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ module GameFaqs
2
+ module Caching
3
+
4
+ protected
5
+ # perform the block only when the name isn't filled already
6
+ def cached_value(name, object=nil, force=false)
7
+ @@cache ||= {}
8
+ if force || @@cache[name].nil?
9
+ @@cache[name] = object
10
+ return_value = yield object
11
+ @@cache[name] = return_value if object.nil?
12
+ end
13
+ @@cache[name]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ module GameFaqs
2
+ class FAQ
3
+ attr_reader :game, :id, :type, :title, :created_at, :author, :version, :size
4
+
5
+ def initialize(options={})
6
+ raise ArgumentError.new("Need at least the game and the faq id") unless options[:game] && options[:id]
7
+ @game, @id, @type, @title, @created_at, @author, @version, @size = options[:game], options[:id], options[:type], options[:title], options[:created_at], options[:author], options[:size]
8
+ end
9
+
10
+ def homepage(refresh=false)
11
+ cached_value("review-#{@id}-#{@game}", [], refresh) do |homepage|
12
+ url = "#{@game.homepage.gsub(/game/, 'file').gsub('.html', '')}/#{@id}"
13
+ doc = Hpricot(open(url))
14
+ doc.search("a[text()='View/Download Original File'") do |a|
15
+ puts a.inner_html
16
+ puts a['href']
17
+ homepage << a['href']
18
+ end
19
+ end.first
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ module GameFaqs
2
+ class Game
3
+
4
+ attr_reader :name, :platform, :homepage, :faqs, :codes
5
+
6
+ def initialize(name, platform, id)
7
+ @name = name
8
+ @platform = platform
9
+ @id = id
10
+ @homepage = "#{@platform.homepage}home/#{@id}.html"
11
+ end
12
+
13
+ def name
14
+ "#{@name} (#{@platform})"
15
+ end
16
+
17
+ def to_s
18
+ "#{@name} [#{platform}]"
19
+ end
20
+
21
+ def reviews(review_type=nil)
22
+ List.reviews(self, review_type)
23
+ end
24
+
25
+ def average_score(review_type=nil)
26
+ sum = reviews(review_type).map{|r| r.score_to_i}.inject{|memo, score| memo + score}
27
+ sum / reviews(review_type).size.to_f
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,124 @@
1
+ module GameFaqs
2
+ module List
3
+ extend Caching
4
+
5
+ PLATFORMS_PATH = "//ul.systems/li/a"
6
+ PLATFORMS_ID_PATH = "//form#search/select[@name=platform]/option"
7
+ GAMES_PATH = "//div.body/table/tr/td/a"
8
+
9
+ class << self
10
+ def platforms(refresh=false)
11
+ cached_value("platforms", [], refresh) do |platforms|
12
+ systems_doc = Hpricot(open("#{GameFaqs::BASE_URL}/systems.html"))
13
+ systems_doc.search(PLATFORMS_PATH).each do |link|
14
+ name = link.inner_html
15
+ platforms << Platform.new({:name => name, :homepage => link['href'], :id => platform_ids[name]})
16
+ end
17
+ end
18
+ end
19
+
20
+ # find all games (very expensive)
21
+ def games(platform, refresh=false)
22
+ cached_value("games", []) do |games|
23
+ letters = ('a'..'z').to_a << '0'
24
+ letters.each do |letter|
25
+ 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
30
+ end
31
+ end
32
+ end
33
+
34
+ def reviews(game, type=nil, refresh=false)
35
+ reviews = cached_value("reviews-#{game.to_s}", [], refresh) do |reviews|
36
+ url = game.homepage.sub(/\/home\//, "/review/")
37
+ 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)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ if type
66
+ types = Review::REVIEW_TYPES
67
+ raise ArgumentError.new("Type must be one of #{types.join(', ')}") unless types.include?(type.to_sym)
68
+ reviews.reject { |review| review.type != type.to_sym}
69
+ else
70
+ reviews
71
+ end
72
+ end
73
+
74
+ def faqs(game, type=nil, refresh=false)
75
+ faqs = cached_value("faqs-#{game.to_s}", [], refresh) do |faqs|
76
+ url = game.homepage.sub(/\/home\//, "/game/")
77
+ 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
89
+ 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
97
+ end
98
+ review[:game] = game
99
+ reviews << Review.new(review)
100
+ end
101
+ end
102
+ end
103
+ if type
104
+ types = FAQ::FAQ_TYPES
105
+ raise ArgumentError.new("Type must be one of #{types.join(', ')}") unless types.include?(type.to_sym)
106
+ faqs.reject { |faq| faq.type != type.to_sym}
107
+ else
108
+ faqs
109
+ end
110
+ end
111
+
112
+ # find all IDs for every platform (cached, do only once)
113
+ def platform_ids(refresh=false)
114
+ cached_value("game_ids", {}, refresh) do |ids|
115
+ search_doc = Hpricot(open(SEARCH_URL))
116
+ search_doc.search(PLATFORMS_ID_PATH).each do |option|
117
+ ids[option['label']] = option['value']
118
+ end
119
+ end
120
+ end
121
+
122
+ end # class < self
123
+ end
124
+ end
@@ -0,0 +1,39 @@
1
+ # coupled to List
2
+ module GameFaqs
3
+ class Platform
4
+ attr_reader :name, :homepage, :id
5
+
6
+ def initialize(params={})
7
+ raise ArgumentError("Need at least the name, homepage, and id of the platform!") unless params[:name] && params[:homepage] && params[:id]
8
+ @name = params[:name]
9
+ @homepage = "#{GameFaqs::BASE_URL}#{params[:homepage]}"
10
+ @id = params[:id] if params[:id]
11
+ end
12
+
13
+ def to_s
14
+ @name
15
+ end
16
+
17
+ def self.all
18
+ List.platforms
19
+ end
20
+
21
+ def self.all_ids
22
+ List.platform_ids
23
+ end
24
+
25
+ def self.all_games
26
+ List.games(self)
27
+ end
28
+
29
+ def find(game, refresh=false)
30
+ Search.game(game, self, refresh)
31
+ end
32
+
33
+ # create case insensitive
34
+ def self.find(platform_name)
35
+ Search.platform(platform_name)
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ module GameFaqs
2
+ class Review
3
+ include Caching
4
+
5
+ REVIEW_TYPES = [:detailed, :full, :quick]
6
+
7
+ attr_reader :game, :id, :score, :author, :title, :type
8
+
9
+ def initialize(options={})
10
+ raise ArgumentError.new("Need at least the game and the review id") unless options[:game] && options[:id]
11
+ @game, @id, @score, @author, @title, @type = options[:game], options[:id], options[:score], options[:author], options[:title], options[:type]
12
+ end
13
+
14
+ def to_s
15
+ "#{@game}: #{@score} by #{author} (#{@title} [#{@type}])"
16
+ end
17
+
18
+ def score_to_i
19
+ actual, max = @score.split("/")
20
+ factor = 10 / max.to_i
21
+ actual.to_i * factor
22
+ end
23
+
24
+ def text
25
+ @text ||= parse_review[:text]
26
+ end
27
+
28
+ def created_at
29
+ @created_at ||= Date.parse(parse_review[:created_at], "%m/%d/%y")
30
+ end
31
+
32
+ def self.all_for(game)
33
+ List.reviews(game)
34
+ end
35
+
36
+ def self.review_type(string)
37
+ REVIEW_TYPES.each do |type|
38
+ return type if string =~ /#{type}/i
39
+ end
40
+ end
41
+
42
+ private
43
+ def parse_review(refresh=false)
44
+ cached_value("review-#{@id}-#{@game.platform}", {}, refresh) do |review|
45
+ url = "#{@game.platform.homepage}review/#{@id}.html"
46
+ doc = Hpricot(open(url))
47
+ doc.search("//div.review/div.details") do |div|
48
+ div.search("p:eq(0)") do |p|
49
+ review[:text] = GameFaqs.strip_html(p.inner_html)
50
+ end
51
+ div.search("p:eq(1)") do |p|
52
+ date = p.inner_html
53
+ date.match(/Originally Posted:.*(\d\d\/\d\d\/\d?\d?\d\d)/)
54
+ review[:created_at] = $1
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,61 @@
1
+ module GameFaqs
2
+ module Search
3
+ extend Caching
4
+ class SearchException < Exception; end
5
+
6
+ # throws a SearchError when no exact match is found
7
+ def self.game(game_name, platform, refresh=false)
8
+ cached_value("search-#{game_name}-#{platform}", nil, refresh) do
9
+ platform = Platform.find(platform) unless platform.is_a?(Platform)
10
+ games = []
11
+ doc = Hpricot(open("#{SEARCH_URL}#{add_params(game_name, platform)}"))
12
+ doc.search("//div.head/h1") do |h1|
13
+ if h1.inner_html =~ /Best Matches/
14
+ h1.search("../../div.body/table") do |table|
15
+ table.search("tr/td/a") do |a|
16
+ if a.inner_html =~ /#{game_name.split(' ').join('.*')}/i
17
+ games << Game.new(a.inner_html.strip, platform, GameFaqs.extract_id(a['href']))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ case games.size
25
+ when 1
26
+ games.first
27
+ when 0
28
+ raise SearchException.new("Could not find a game containing the string \"#{game_name}\" on platform \"#{platform}\"!")
29
+ else
30
+ raise SearchException.new("Found more than one game containing the string \"#{game_name}\": #{games.join(', ')}")
31
+ end
32
+ end
33
+ end
34
+
35
+ class << self
36
+
37
+ def platform(platform_name)
38
+ # get by full name (case insensitive)
39
+ names = List.platforms.select { |p| p.name.downcase == platform_name.downcase }
40
+ # find other similar if not found exactly one before
41
+ if names.size != 1
42
+ names = List.platforms.select{|p| p.name.downcase =~ /#{platform_name.split(' ').join('.*')}/i}
43
+ end
44
+
45
+ case names.size
46
+ when 1
47
+ names.first
48
+ when 0
49
+ raise SearchException.new("Could not find a platform containing the string \"#{platform_name}\"!")
50
+ else
51
+ raise SearchException.new("Found more than one platform containing the string \"#{platform_name}\": #{names.join(', ')}")
52
+ end
53
+ end
54
+
55
+ private
56
+ def add_params(keywords, platform)
57
+ "?game=#{keywords.gsub(/ /, '+')}" << "&platform=#{platform.id}"
58
+ end
59
+ end
60
+ end
61
+ end