steam-condenser 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.md +2 -1
  2. data/Rakefile +10 -1
  3. data/lib/steam-condenser/version.rb +1 -1
  4. data/lib/steam/community/app_news.rb +0 -0
  5. data/lib/steam/community/game_leaderboard.rb +199 -0
  6. data/lib/steam/community/game_leaderboard_entry.rb +43 -0
  7. data/lib/steam/community/game_stats.rb +16 -0
  8. data/lib/steam/community/steam_game.rb +18 -0
  9. data/lib/steam/community/steam_group.rb +9 -4
  10. data/lib/steam/community/steam_id.rb +45 -41
  11. data/lib/steam/community/tf2/tf2_beta_inventory.rb +22 -0
  12. data/lib/steam/community/tf2/tf2_golden_wrench.rb +0 -0
  13. data/lib/steam/community/tf2/tf2_stats.rb +10 -4
  14. data/lib/steam/community/web_api.rb +0 -0
  15. data/lib/steam/servers/master_server.rb +2 -1
  16. data/lib/steam/servers/server.rb +1 -1
  17. data/lib/steam/sockets/rcon_socket.rb +0 -1
  18. data/lib/steam/sockets/steam_socket.rb +2 -1
  19. data/steam-condenser.gemspec +5 -4
  20. data/test/core_ext/test_stringio.rb +59 -0
  21. data/test/fixtures/invalid.xml +2 -0
  22. data/test/fixtures/sonofthor.xml +882 -0
  23. data/test/fixtures/status_goldsrc +3 -0
  24. data/test/fixtures/status_source +3 -0
  25. data/test/fixtures/valve-members.xml +231 -0
  26. data/test/helper.rb +37 -0
  27. data/test/steam/communtiy/test_steam_group.rb +83 -0
  28. data/test/steam/communtiy/test_steam_id.rb +85 -0
  29. data/test/steam/communtiy/test_web_api.rb +98 -0
  30. data/test/steam/servers/test_game_server.rb +308 -0
  31. data/test/steam/servers/test_goldsrc_server.rb +59 -0
  32. data/test/steam/servers/test_master_server.rb +131 -0
  33. data/test/steam/servers/test_server.rb +72 -0
  34. data/test/steam/servers/test_source_server.rb +140 -0
  35. data/test/steam/sockets/test_goldsrc_socket.rb +127 -0
  36. data/test/steam/sockets/test_master_server_socket.rb +41 -0
  37. data/test/steam/sockets/test_rcon_socket.rb +109 -0
  38. data/test/steam/sockets/test_source_socket.rb +77 -0
  39. data/test/steam/sockets/test_steam_socket.rb +97 -0
  40. metadata +94 -34
  41. data/Gemfile.lock +0 -22
  42. data/test/query_tests.rb +0 -70
  43. data/test/rcon_tests.rb +0 -78
  44. data/test/steam/communtiy/steam_community_test_suite.rb +0 -12
  45. data/test/steam/communtiy/steam_group_tests.rb +0 -44
  46. data/test/steam/communtiy/steam_id_tests.rb +0 -49
  47. data/test/steam_community_tests.rb +0 -45
  48. data/test/stringio_additions_tests.rb +0 -77
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Steam Condenser
1
+ Steam Condenser [![Build Status](https://secure.travis-ci.org/koraktor/steam-condenser-ruby.png)](http://travis-ci.org/koraktor/steam-condenser-ruby)
2
2
  ===============
3
3
 
4
4
  The Steam Condenser is a multi-language library for querying the Steam
@@ -31,6 +31,7 @@ included LICENSE file.
31
31
 
32
32
  * Sebastian Staudt -- koraktor(at)gmail.com
33
33
  * DeFirence -- defirence(at)defirence.za.net
34
+ * Mike Połtyn -- mike(at)railslove.com
34
35
 
35
36
  ## See Also
36
37
 
data/Rakefile CHANGED
@@ -5,12 +5,21 @@
5
5
  #
6
6
  # Copyright (c) 2008-2011, Sebastian Staudt
7
7
 
8
- require 'rubygems'
8
+ require 'rake/testtask'
9
9
  require 'rubygems/package_task'
10
10
 
11
+ task :default => :test
12
+
11
13
  Gem::PackageTask.new Gem::Specification.load 'steam-condenser.gemspec' do |pkg|
12
14
  end
13
15
 
16
+ # Rake task for running the test suite
17
+ Rake::TestTask.new do |t|
18
+ t.libs << 'lib' << 'test'
19
+ t.test_files = Dir.glob 'test/**/test_*.rb'
20
+ t.verbose = true
21
+ end
22
+
14
23
  # Check if YARD is installed
15
24
  begin
16
25
  require 'yard'
@@ -6,6 +6,6 @@
6
6
  module SteamCondenser
7
7
 
8
8
  # The current version of Steam Condenser
9
- VERSION = '1.0.2'
9
+ VERSION = '1.1.0'
10
10
 
11
11
  end
File without changes
@@ -0,0 +1,199 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ require 'steam/community/game_leaderboard_entry';
7
+ require 'steam/community/steam_id';
8
+
9
+ # The GameLeaderboard class represents a single leaderboard for a specific game
10
+ #
11
+ # @author Sebastian Staudt
12
+ class GameLeaderboard
13
+
14
+ LEADERBOARD_DISPLAY_TYPE_NONE = 0
15
+ LEADERBOARD_DISPLAY_TYPE_NUMERIC = 1
16
+ LEADERBOARD_DISPLAY_TYPE_SECONDS = 2
17
+ LEADERBOARD_DISPLAY_TYPE_MILLISECONDS = 3
18
+
19
+ LEADERBOARD_SORT_METHOD_NONE = 0
20
+ LEADERBOARD_SORT_METHOD_ASC = 1
21
+ LEADERBOARD_SORT_METHOD_DESC = 2
22
+
23
+ @@leaderboards = {}
24
+
25
+ # Returns the display type of the scores on this leaderboard
26
+ #
27
+ # @return [Fixnum] The display type of the scores
28
+ attr_reader :display_type
29
+
30
+ # Returns the number of entries on this leaderboard
31
+ #
32
+ # @return [Fixnum] The number of entries on this leaderboard
33
+ attr_reader :entry_count
34
+
35
+ # Returns the ID of the leaderboard
36
+ #
37
+ # @return [Fixnum] The ID of the leaderboard
38
+ attr_reader :id
39
+
40
+ # Returns the name of the leaderboard
41
+ #
42
+ # @return [String] The name of the leaderboard
43
+ attr_reader :name
44
+
45
+ # Returns the method that is used to sort the entries on the leaderboard
46
+ #
47
+ # @return [Fixnum] The sort method
48
+ attr_reader :sort_method
49
+
50
+ # Returns the leaderboard for the given game and leaderboard ID or name
51
+ #
52
+ # @param [String] game_name The short name of the game
53
+ # @param [Fixnum, String] id The ID or name of the leaderboard to return
54
+ # @return [GameLeaderboard] The matching leaderboard if available
55
+ def self.leaderboard(game_name, id)
56
+ leaderboards = self.leaderboards game_name
57
+
58
+ if id.is_a? Fixnum
59
+ leaderboards[id]
60
+ else
61
+ leaderboards.each_value do |board|
62
+ return board if board.name == id
63
+ end
64
+ end
65
+ end
66
+
67
+ # Returns an array containing all of a game's leaderboards
68
+ #
69
+ # @param [String] game_name The name of the game
70
+ # @return [Array<GameLeaderboard>] The leaderboards for this game
71
+ def self.leaderboards(game_name)
72
+ self.load_leaderboards game_name unless @@leaderboards.key? game_name
73
+
74
+ @@leaderboards[game_name]
75
+ end
76
+
77
+ # Returns the entry on this leaderboard for the user with the given SteamID
78
+ #
79
+ # @param [Fixnum, SteamId] steam_id The 64bit SteamID or the `SteamId` object
80
+ # of the user
81
+ # @return [GameLeaderboardEntry] The entry of the user if available
82
+ # raise [SteamCondenserException] if an error occurs while fetching the
83
+ # leaderboard
84
+ def entry_for_steam_id(steam_id)
85
+ steam_id = steam_id.steam_id64 if steam_id.is_a? SteamId
86
+
87
+ url = "#{@url}&steamid=#{steam_id}"
88
+ xml = REXML::Document.new(open(url, {:proxy => true}).read).root
89
+
90
+ error = @xml_data.elements['error']
91
+ raise SteamCondenserError, error.text unless error.nil?
92
+
93
+ boards_data.elements.each('entries/entry') do |entry_data|
94
+ if entry_data.elements['steamid'].text.to_i == steam_id
95
+ return GameLeaderboardEntry.new entry_data, self
96
+ end
97
+ end
98
+
99
+ nil
100
+ end
101
+
102
+ # Returns an array of entries on this leaderboard for the user with the given
103
+ # SteamID and his/her friends
104
+ #
105
+ # @param [Fixnum, SteamId] steam_id The 64bit SteamID or the `SteamId` object
106
+ # of the user
107
+ # @return [Array<GameLeaderboardEntry>] The entries of the user and his/her
108
+ # friends
109
+ # raise [SteamCondenserException] if an error occurs while fetching the
110
+ # leaderboard
111
+ def entry_for_steam_id_friends(steam_id)
112
+ steam_id = steam_id.steam_id64 if steam_id.is_a? SteamId
113
+
114
+ url = "#{@url}&steamid=#{steam_id}"
115
+ xml = REXML::Document.new(open(url, {:proxy => true}).read).root
116
+
117
+ error = @xml_data.elements['error']
118
+ raise SteamCondenserError, error.text unless error.nil?
119
+
120
+ entries = []
121
+ boards_data.elements.each('entries/entry') do |entry_data|
122
+ rank = entry_data.elements['rank'].text.to_i
123
+ entries[rank] = GameLeaderboardEntry.new entry_data, self
124
+ end
125
+
126
+ entries
127
+ end
128
+
129
+ # Returns the entries on this leaderboard for a given rank range
130
+ #
131
+ # The range is inclusive and a maximum of 5001 entries can be returned in a
132
+ # single request.
133
+ #
134
+ # @param [Fixnum] first The first entry to return from the leaderboard
135
+ # @param [Fixnum] last The last entry to return from the leaderboard
136
+ # @return [Array<GameLeaderboardEntry>] The entries that match the given rank
137
+ # range
138
+ # raise [SteamCondenserException] if an error occurs while fetching the
139
+ # leaderboard
140
+ def entry_range(first, last)
141
+ if last < first
142
+ raise SteamCondenserException,
143
+ 'First entry must be prior to last entry for leaderboard entry lookup.'
144
+ end
145
+
146
+ if (last - first) > 5000
147
+ raise SteamCondenserException,
148
+ 'Leaderboard entry lookup is currently limited to a maximum of 5001 entries per request.'
149
+ end
150
+
151
+ url = "#{@url}&start=#{first}&end=#{last}"
152
+ xml = REXML::Document.new(open(url, {:proxy => true}).read).root
153
+
154
+ error = @xml_data.elements['error']
155
+ raise SteamCondenserError, error.text unless error.nil?
156
+
157
+ entries = []
158
+ boards_data.elements.each('entries/entry') do |entry_data|
159
+ rank = entry_data.elements['rank'].text.to_i
160
+ entries[rank] = GameLeaderboardEntry.new entry_data, self
161
+ end
162
+
163
+ entries
164
+ end
165
+
166
+ private
167
+
168
+ # Creates a new leaderboard instance with the given XML data
169
+ #
170
+ # @param [REXML::Element] board_data The XML data of the leaderboard
171
+ def initialize(board_data)
172
+ @url = board_data.elements['url'].text
173
+ @id = board_data.elements['lbid'].text.to_i
174
+ @name = board_data.elements['name'].text
175
+ @entry_count = board_data.elements['entries'].text.to_i
176
+ @sort_method = board_data.elements['sortmethod'].text.to_i
177
+ @display_type = board_data.elements['displaytype'].text.to_i
178
+ end
179
+
180
+ # Loads the leaderboards of the specified games into the cache
181
+ #
182
+ # @param [String] game_name The short name of the game
183
+ # @raise [SteamCondenserException] if an error occurs while fetching the
184
+ # leaderboards
185
+ def self.load_leaderboards(game_name)
186
+ url = "http://steamcommunity.com/stats/#{game_name}/leaderboards/?xml=1"
187
+ boards_data = REXML::Document.new(open(url, {:proxy => true}).read).root
188
+
189
+ error = boards_data.elements['error']
190
+ raise SteamCondenserError, error.text unless error.nil?
191
+
192
+ @@leaderboards[game_name] = []
193
+ boards_data.elements.each('leaderboard') do |board_data|
194
+ leaderboard = GameLeaderboard.new board_data
195
+ @@leaderboards[game_name][leaderboard.id] = leaderboard
196
+ end
197
+ end
198
+
199
+ end
@@ -0,0 +1,43 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ # The GameLeaderboard class represents a single entry in a leaderboard
7
+ #
8
+ # @author Sebastian Staudt
9
+ class GameLeaderboardEntry
10
+
11
+ # Returns the Steam ID of this entry's player
12
+ #
13
+ # @return [SteamId] The Steam ID of the player
14
+ attr_reader :steam_id
15
+
16
+ # Returns the score of this entry
17
+ #
18
+ # @return [Fixnum] The score of this player
19
+ attr_reader :score
20
+
21
+ # Returns the rank where this entry is listed in the leaderboard
22
+ #
23
+ # @return [Fixnum] The rank of this entry
24
+ attr_reader :rank
25
+
26
+ # Returns the leaderboard this entry belongs to
27
+ #
28
+ # @return [GameLeaderboard] The leaderboard of this entry
29
+ attr_reader :leaderboard
30
+
31
+ # Creates new entry instance for the given XML data and leaderboard
32
+ #
33
+ # @param [REXML::Element] entry_data The XML data of the leaderboard of the
34
+ # leaderboard entry
35
+ # @param [GameLeaderboard] leaderboard The leaderboard this entry belongs to
36
+ def initialize(entry_data, leaderboard)
37
+ @steam_id = SteamId.new entry_data.elements['steamid'].text.to_i, false
38
+ @score = entry_data.elements['score'].text.to_i
39
+ @rank = entry_data.elements['rank'].text.to_i
40
+ @leaderboard = leaderboard
41
+ end
42
+
43
+ end
@@ -7,6 +7,7 @@ require 'open-uri'
7
7
  require 'rexml/document'
8
8
 
9
9
  require 'steam/community/game_achievement'
10
+ require 'steam/community/game_leaderboard'
10
11
 
11
12
  # This class represents the game statistics for a single user and a specific
12
13
  # game
@@ -175,6 +176,21 @@ class GameStats
175
176
  end
176
177
  end
177
178
 
179
+ # Returns the leaderboard for this game and the given leaderboard ID or name
180
+ #
181
+ # @param [Fixnum, String] id The ID or name of the leaderboard to return
182
+ # @return [GameLeaderboard] The matching leaderboard if available
183
+ def leaderboard(id)
184
+ GameLeaderboard.leaderboard @game_friendly_name, id
185
+ end
186
+
187
+ # Returns an array containing all of this game's leaderboards
188
+ #
189
+ # @return [Array<GameLeaderboard>] The leaderboards for this game
190
+ def leaderboards
191
+ GameLeaderboard.leaderboards @game_friendly_name
192
+ end
193
+
178
194
  # Returns whether this Steam ID is publicly accessible
179
195
  #
180
196
  # @return [Boolean] `true` if this Steam ID is publicly accessible
@@ -3,6 +3,9 @@
3
3
  #
4
4
  # Copyright (c) 2011, Sebastian Staudt
5
5
 
6
+ require 'steam/community/game_leaderboard'
7
+ require 'steam/community/game_stats'
8
+
6
9
  # This class represents a game available on Steam
7
10
  #
8
11
  # @author Sebastian Staudt
@@ -42,6 +45,21 @@ class SteamGame
42
45
  !@short_name.nil?
43
46
  end
44
47
 
48
+ # Returns the leaderboard for this game and the given leaderboard ID or name
49
+ #
50
+ # @param [Fixnum, String] id The ID or name of the leaderboard to return
51
+ # @return [GameLeaderboard] The matching leaderboard if available
52
+ def leaderboard(id)
53
+ GameLeaderboard.leaderboard @short_name, id
54
+ end
55
+
56
+ # Returns an array containing all of this game's leaderboards
57
+ #
58
+ # @return [Array<GameLeaderboard>] The leaderboards for this game
59
+ def leaderboards
60
+ GameLeaderboard.leaderboards @short_name
61
+ end
62
+
45
63
  # Creates a stats object for the given user and this game
46
64
  #
47
65
  # @param [String, Fixnum] steam_id The custom URL or the 64bit Steam ID of
@@ -6,6 +6,7 @@
6
6
  require 'open-uri'
7
7
  require 'rexml/document'
8
8
 
9
+ require 'errors/steam_condenser_error'
9
10
  require 'steam/community/cacheable'
10
11
  require 'steam/community/steam_id'
11
12
 
@@ -78,11 +79,15 @@ class SteamGroup
78
79
  url = open("#{base_url}/memberslistxml?p=#{page}", {:proxy => true})
79
80
  member_data = REXML::Document.new(url.read).root
80
81
 
81
- @group_id64 = member_data.elements['groupID64'].text.to_i if page == 1
82
- total_pages = member_data.elements['totalPages'].text.to_i
82
+ begin
83
+ @group_id64 = member_data.elements['groupID64'].text.to_i if page == 1
84
+ total_pages = member_data.elements['totalPages'].text.to_i
83
85
 
84
- member_data.elements['members'].elements.each do |member|
85
- @members << SteamId.new(member.text.to_i, false)
86
+ member_data.elements['members'].elements.each do |member|
87
+ @members << SteamId.new(member.text.to_i, false)
88
+ end
89
+ rescue
90
+ raise SteamCondenserError, 'XML data could not be parsed.'
86
91
  end
87
92
  end while page < total_pages
88
93
 
@@ -232,57 +232,61 @@ class SteamId
232
232
  raise SteamCondenserError, profile.elements['error'].text
233
233
  end
234
234
 
235
- @nickname = CGI.unescapeHTML profile.elements['steamID'].text
236
- @steam_id64 = profile.elements['steamID64'].text.to_i
237
- @vac_banned = (profile.elements['vacBanned'].text == 1)
238
-
239
235
  unless REXML::XPath.first(profile, 'privacyMessage').nil?
240
236
  raise SteamCondenserError, profile.elements['privacyMessage'].text
241
237
  end
238
+
239
+ begin
240
+ @nickname = CGI.unescapeHTML profile.elements['steamID'].text
241
+ @steam_id64 = profile.elements['steamID64'].text.to_i
242
+ @vac_banned = (profile.elements['vacBanned'].text == 1)
243
+
244
+ @image_url = profile.elements['avatarIcon'].text[0..-5]
245
+ @online_state = profile.elements['onlineState'].text
246
+ @privacy_state = profile.elements['privacyState'].text
247
+ @state_message = profile.elements['stateMessage'].text
248
+ @visibility_state = profile.elements['visibilityState'].text.to_i
249
+
250
+ if @privacy_state == 'public'
251
+ @custom_url = profile.elements['customURL'].text.downcase
252
+ @custom_url = nil if @custom_url.empty?
253
+
254
+ unless REXML::XPath.first(profile, 'favoriteGame').nil?
255
+ @favorite_game = profile.elements['favoriteGame/name'].text
256
+ @favorite_game_hours_played = profile.elements['favoriteGame/hoursPlayed2wk'].text
257
+ end
242
258
 
243
- @image_url = profile.elements['avatarIcon'].text[0..-5]
244
- @online_state = profile.elements['onlineState'].text
245
- @privacy_state = profile.elements['privacyState'].text
246
- @state_message = profile.elements['stateMessage'].text
247
- @visibility_state = profile.elements['visibilityState'].text.to_i
248
-
249
- if @privacy_state == 'public'
250
- @custom_url = profile.elements['customURL'].text.downcase
251
- @custom_url = nil if @custom_url.empty?
252
-
253
- unless REXML::XPath.first(profile, 'favoriteGame').nil?
254
- @favorite_game = profile.elements['favoriteGame/name'].text
255
- @favorite_game_hours_played = profile.elements['favoriteGame/hoursPlayed2wk'].text
256
- end
257
-
258
- @head_line = CGI.unescapeHTML profile.elements['headline'].text
259
- @hours_played = profile.elements['hoursPlayed2Wk'].text.to_f
260
- @location = profile.elements['location'].text
261
- @member_since = Time.parse(profile.elements['memberSince'].text)
262
- @real_name = CGI.unescapeHTML profile.elements['realname'].text
263
- @steam_rating = profile.elements['steamRating'].text.to_f
264
- @summary = CGI.unescapeHTML profile.elements['summary'].text
265
-
266
- @most_played_games = {}
267
- unless REXML::XPath.first(profile, 'mostPlayedGames').nil?
268
- profile.elements.each('mostPlayedGames/mostPlayedGame') do |most_played_game|
269
- @most_played_games[most_played_game.elements['gameName'].text] = most_played_game.elements['hoursPlayed'].text.to_f
259
+ @head_line = CGI.unescapeHTML profile.elements['headline'].text
260
+ @hours_played = profile.elements['hoursPlayed2Wk'].text.to_f
261
+ @location = profile.elements['location'].text
262
+ @member_since = Time.parse(profile.elements['memberSince'].text)
263
+ @real_name = CGI.unescapeHTML profile.elements['realname'].text
264
+ @steam_rating = profile.elements['steamRating'].text.to_f
265
+ @summary = CGI.unescapeHTML profile.elements['summary'].text
266
+
267
+ @most_played_games = {}
268
+ unless REXML::XPath.first(profile, 'mostPlayedGames').nil?
269
+ profile.elements.each('mostPlayedGames/mostPlayedGame') do |most_played_game|
270
+ @most_played_games[most_played_game.elements['gameName'].text] = most_played_game.elements['hoursPlayed'].text.to_f
271
+ end
270
272
  end
271
- end
272
273
 
273
- @groups = []
274
- unless REXML::XPath.first(profile, 'groups').nil?
275
- profile.elements.each('groups/group') do |group|
276
- @groups << SteamGroup.new(group.elements['groupID64'].text.to_i, false)
274
+ @groups = []
275
+ unless REXML::XPath.first(profile, 'groups').nil?
276
+ profile.elements.each('groups/group') do |group|
277
+ @groups << SteamGroup.new(group.elements['groupID64'].text.to_i, false)
278
+ end
277
279
  end
278
- end
279
280
 
280
- @links = {}
281
- unless REXML::XPath.first(profile, 'mostPlayedGames').nil?
282
- profile.elements.each('weblinks/weblink') do |link|
283
- @links[CGI.unescapeHTML link.elements['title'].text] = link.elements['link'].text
281
+ @links = {}
282
+ unless REXML::XPath.first(profile, 'weblinks').nil?
283
+ profile.elements.each('weblinks/weblink') do |link|
284
+ @links[CGI.unescapeHTML link.elements['title'].text] = link.elements['link'].text
285
+ end
284
286
  end
285
287
  end
288
+ rescue
289
+ raise SteamCondenserError, 'XML data could not be parsed.'
286
290
  end
287
291
 
288
292
  super