steam-condenser 1.0.2 → 1.1.0

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