steam-condenser 0.14.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +22 -0
  3. data/LICENSE +1 -1
  4. data/README.md +14 -6
  5. data/Rakefile +35 -0
  6. data/lib/{stringio_additions.rb → core_ext/stringio.rb} +1 -1
  7. data/lib/{exceptions/packet_format_exception.rb → errors/packet_format_error.rb} +5 -5
  8. data/lib/{exceptions/rcon_ban_exception.rb → errors/rcon_ban_error.rb} +5 -5
  9. data/lib/{exceptions/rcon_no_auth_exception.rb → errors/rcon_no_auth_error.rb} +5 -5
  10. data/lib/{exceptions/steam_condenser_exception.rb → errors/steam_condenser_error.rb} +3 -3
  11. data/lib/errors/timeout_error.rb +28 -0
  12. data/lib/{exceptions/web_api_exception.rb → errors/web_api_error.rb} +8 -8
  13. data/lib/steam/community/alien_swarm/alien_swarm_mission.rb +86 -11
  14. data/lib/steam/community/alien_swarm/alien_swarm_stats.rb +38 -15
  15. data/lib/steam/community/alien_swarm/alien_swarm_weapon.rb +29 -8
  16. data/lib/steam/community/app_news.rb +91 -27
  17. data/lib/steam/community/cacheable.rb +65 -21
  18. data/lib/steam/community/css/css_map.rb +39 -9
  19. data/lib/steam/community/css/css_stats.rb +32 -12
  20. data/lib/steam/community/css/css_weapon.rb +46 -10
  21. data/lib/steam/community/defense_grid/defense_grid_stats.rb +129 -17
  22. data/lib/steam/community/dods/dods_class.rb +66 -10
  23. data/lib/steam/community/dods/dods_stats.rb +20 -7
  24. data/lib/steam/community/dods/dods_weapon.rb +35 -24
  25. data/lib/steam/community/game_achievement.rb +50 -19
  26. data/lib/steam/community/game_class.rb +16 -5
  27. data/lib/steam/community/game_inventory.rb +37 -4
  28. data/lib/steam/community/game_item.rb +64 -3
  29. data/lib/steam/community/game_stats.rb +81 -16
  30. data/lib/steam/community/game_weapon.rb +29 -11
  31. data/lib/steam/community/l4d/abstract_l4d_stats.rb +91 -65
  32. data/lib/steam/community/l4d/abstract_l4d_weapon.rb +38 -8
  33. data/lib/steam/community/l4d/l4d2_map.rb +30 -5
  34. data/lib/steam/community/l4d/l4d2_stats.rb +83 -45
  35. data/lib/steam/community/l4d/l4d2_weapon.rb +20 -6
  36. data/lib/steam/community/l4d/l4d_explosive.rb +13 -5
  37. data/lib/steam/community/l4d/l4d_map.rb +35 -7
  38. data/lib/steam/community/l4d/l4d_stats.rb +23 -10
  39. data/lib/steam/community/l4d/l4d_weapon.rb +11 -7
  40. data/lib/steam/community/portal2/portal2_inventory.rb +4 -0
  41. data/lib/steam/community/portal2/portal2_item.rb +13 -1
  42. data/lib/steam/community/portal2/portal2_stats.rb +10 -6
  43. data/lib/steam/community/steam_game.rb +74 -0
  44. data/lib/steam/community/steam_group.rb +48 -14
  45. data/lib/steam/community/steam_id.rb +295 -64
  46. data/lib/steam/community/tf2/tf2_class.rb +66 -7
  47. data/lib/steam/community/tf2/tf2_class_factory.rb +14 -7
  48. data/lib/steam/community/tf2/tf2_engineer.rb +26 -6
  49. data/lib/steam/community/tf2/tf2_golden_wrench.rb +37 -10
  50. data/lib/steam/community/tf2/tf2_inventory.rb +4 -0
  51. data/lib/steam/community/tf2/tf2_item.rb +13 -1
  52. data/lib/steam/community/tf2/tf2_medic.rb +20 -5
  53. data/lib/steam/community/tf2/tf2_sniper.rb +15 -5
  54. data/lib/steam/community/tf2/tf2_spy.rb +20 -6
  55. data/lib/steam/community/tf2/tf2_stats.rb +20 -6
  56. data/lib/steam/community/web_api.rb +50 -32
  57. data/lib/steam/packets/c2m_checkmd5_packet.rb +1 -1
  58. data/lib/steam/packets/m2a_server_batch_packet.rb +3 -2
  59. data/lib/steam/packets/m2s_requestrestart_packet.rb +3 -3
  60. data/lib/steam/packets/rcon/rcon_packet_factory.rb +4 -4
  61. data/lib/steam/packets/request_with_challenge.rb +1 -1
  62. data/lib/steam/packets/s2a_info_base_packet.rb +1 -1
  63. data/lib/steam/packets/s2a_info_detailed_packet.rb +10 -10
  64. data/lib/steam/packets/s2a_player_packet.rb +5 -1
  65. data/lib/steam/packets/s2a_rules_packet.rb +4 -3
  66. data/lib/steam/packets/s2m_heartbeat2_packet.rb +2 -2
  67. data/lib/steam/packets/steam_packet.rb +1 -1
  68. data/lib/steam/packets/steam_packet_factory.rb +12 -16
  69. data/lib/steam/servers/game_server.rb +38 -32
  70. data/lib/steam/servers/goldsrc_server.rb +10 -1
  71. data/lib/steam/servers/master_server.rb +42 -21
  72. data/lib/steam/servers/server.rb +4 -5
  73. data/lib/steam/servers/source_server.rb +20 -5
  74. data/lib/steam/sockets/goldsrc_socket.rb +53 -22
  75. data/lib/steam/sockets/master_server_socket.rb +14 -4
  76. data/lib/steam/sockets/rcon_socket.rb +39 -6
  77. data/lib/steam/sockets/source_socket.rb +19 -15
  78. data/lib/steam/sockets/steam_socket.rb +33 -23
  79. data/lib/steam/steam_player.rb +86 -11
  80. data/lib/steam-condenser/community.rb +24 -24
  81. data/lib/steam-condenser/servers.rb +2 -2
  82. data/lib/steam-condenser/version.rb +3 -3
  83. data/steam-condenser.gemspec +23 -0
  84. data/test/query_tests.rb +12 -12
  85. data/test/rcon_tests.rb +3 -3
  86. data/test/steam/communtiy/steam_community_test_suite.rb +12 -0
  87. data/test/steam/communtiy/steam_group_tests.rb +6 -5
  88. data/test/steam/communtiy/steam_id_tests.rb +6 -5
  89. data/test/steam_community_tests.rb +3 -3
  90. data/test/stringio_additions_tests.rb +7 -7
  91. metadata +61 -43
  92. data/lib/exceptions/timeout_exception.rb +0 -24
  93. data/test/datagram_channel_tests.rb +0 -42
  94. data/test/socket_channel_tests.rb +0 -43
@@ -1,36 +1,149 @@
1
- # This code is free software; you can redistribute it and/or modify it under the
2
- # terms of the new BSD License.
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2010, Sebastian Staudt
4
+ # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
6
+ require 'cgi'
6
7
  require 'open-uri'
7
8
  require 'rexml/document'
8
9
 
9
- require 'exceptions/steam_condenser_exception'
10
+ require 'errors/steam_condenser_error'
10
11
  require 'steam/community/cacheable'
11
12
  require 'steam/community/game_stats'
13
+ require 'steam/community/steam_game'
12
14
  require 'steam/community/steam_group'
13
15
 
14
16
  # The SteamId class represents a Steam Community profile (also called Steam ID)
17
+ #
18
+ # @author Sebastian Staudt
15
19
  class SteamId
16
20
 
17
21
  include Cacheable
18
22
  cacheable_with_ids :custom_url, :steam_id64
19
23
 
20
- attr_reader :custom_url, :favorite_game, :favorite_game_hours_played,
21
- :groups, :head_line, :hours_played, :image_url, :links, :location,
22
- :member_since, :most_played_games, :nickname, :privacy_state,
23
- :real_name, :state_message, :steam_id64, :steam_rating,
24
- :steam_rating_text, :summary, :vac_banned, :visibility_state
25
-
26
- # Converts the 64bit SteamID +community_id+ as used and reported by the Steam
27
- # Community to a SteamID reported by game servers
24
+ # Returns the custom URL of this Steam ID
25
+ #
26
+ # The custom URL is a user specified unique string that can be used instead
27
+ # of the 64bit SteamID as an identifier for a Steam ID.
28
+ #
29
+ # @note The custom URL is not necessarily the same as the user's nickname.
30
+ # @return [String] The custom URL of this Steam ID
31
+ attr_reader :custom_url
32
+
33
+ # Returns the favorite game of this user
34
+ #
35
+ # @deprecated The favorite game is no longer listed for new users
36
+ # @return [String] The favorite game of this user
37
+ attr_reader :favorite_game
38
+
39
+ # Returns the number of hours that this user played his/her favorite game in
40
+ # the last two weeks
41
+ #
42
+ # @deprecated The favorite game is no longer listed for new users
43
+ # @return [String] The number of hours the favorite game has been played
44
+ # recently
45
+ attr_reader :favorite_game_hours_played
46
+
47
+ # Returns the groups this user is a member of
48
+ #
49
+ # @return [Array<SteamGroup>] The groups this user is a member of
50
+ attr_reader :groups
51
+
52
+ # Returns the headline specified by the user
53
+ #
54
+ # @return [String] The headline specified by the user
55
+ attr_reader :head_line
56
+
57
+ # Returns the number of hours that this user played a game in the last two
58
+ # weeks
59
+ #
60
+ # @return [Float] The number of hours the user has played recently
61
+ attr_reader :hours_played
62
+
63
+ # Returns the links that this user has added to his/her Steam ID
64
+ #
65
+ # The keys of the hash contain the titles of the links while the values
66
+ # contain the corresponding URLs.
67
+ #
68
+ # @return [Hash<String, String>] The links of this user
69
+ attr_reader :links
70
+
71
+ # Returns the location of the user
72
+ #
73
+ # @return [String] The location of the user
74
+ attr_reader :location
75
+
76
+ # Returns the date of registration for the Steam account belonging to this
77
+ # SteamID
78
+ #
79
+ # @return [Time] The date of the Steam account registration
80
+ attr_reader :member_since
81
+
82
+ # Returns the games this user has played the most in the last two weeks
83
+ #
84
+ # The keys of the hash contain the names of the games while the values
85
+ # contain the number of hours the corresponding game has been played by the
86
+ # user in the last two weeks.
87
+ #
88
+ # @return [Hash<String, Float>] The games this user has played the most
89
+ # recently
90
+ attr_reader :most_played_games
91
+
92
+ # Returns the Steam nickname of the user
93
+ #
94
+ # @return [String] The Steam nickname of the user
95
+ attr_reader :nickname
96
+
97
+ # Returns the privacy state of this Steam ID
98
+ #
99
+ # @return [String] The privacy state of this Steam ID
100
+ attr_reader :privacy_state
101
+
102
+ # Returns the real name of this user
103
+ #
104
+ # @return [String] The real name of this user
105
+ attr_reader :real_name
106
+
107
+ # Returns the message corresponding to this user's online state
108
+ #
109
+ # @return [String] The message corresponding to this user's online state
110
+ # @see #ingame?
111
+ # @see #online?
112
+ attr_reader :state_message
113
+
114
+ # Returns this user's 64bit SteamID
115
+ #
116
+ # @return [Fixnum] This user's 64bit SteamID
117
+ attr_reader :steam_id64
118
+
119
+ # Returns the Steam rating calculated over the last two weeks' activity
120
+ #
121
+ # @return [Float] The Steam rating of this user
122
+ attr_reader :steam_rating
123
+
124
+ # Returns the summary this user has provided
125
+ #
126
+ # @return [String] This user's summary
127
+ attr_reader :summary
128
+
129
+ # Returns the visibility state of this Steam ID
130
+ #
131
+ # @return [Fixnum] This Steam ID's visibility State
132
+ attr_reader :visibility_state
133
+
134
+ # Converts a 64bit numeric SteamID as used by the Steam Community to a
135
+ # SteamID as reported by game servers
136
+ #
137
+ # @param [Fixnum] community_id The SteamID string as used by the Steam
138
+ # Community
139
+ # @raise [SteamCondenserError] if the community ID is to small
140
+ # @return [String] The converted SteamID, like `STEAM_0:0:12345`
28
141
  def self.convert_community_id_to_steam_id(community_id)
29
142
  steam_id1 = community_id % 2
30
143
  steam_id2 = community_id - 76561197960265728
31
144
 
32
145
  unless steam_id2 > 0
33
- raise SteamCondenserException.new("SteamID #{community_id} is too small.")
146
+ raise SteamCondenserError, "SteamID #{community_id} is too small."
34
147
  end
35
148
 
36
149
  steam_id2 = (steam_id2 - steam_id1) / 2
@@ -38,13 +151,19 @@ class SteamId
38
151
  "STEAM_0:#{steam_id1}:#{steam_id2}"
39
152
  end
40
153
 
41
- # Converts the SteamID +steam_id+ as reported by game servers to a 64bit
42
- # SteamID
154
+ # Converts a SteamID as reported by game servers to a 64bit numeric SteamID
155
+ # as used by the Steam Community
156
+ #
157
+ # @param [String] steam_id The SteamID string as used on servers, like
158
+ # `STEAM_0:0:12345`
159
+ # @raise [SteamCondenserError] if the SteamID doesn't have the correct
160
+ # format
161
+ # @return [Fixnum] The converted 64bit numeric SteamID
43
162
  def self.convert_steam_id_to_community_id(steam_id)
44
- if steam_id == 'STEAM_ID_LAN' or steam_id == 'BOT'
45
- raise SteamCondenserException.new("Cannot convert SteamID \"#{steam_id}\" to a community ID.")
163
+ if steam_id == 'STEAM_ID_LAN' || steam_id == 'BOT'
164
+ raise SteamCondenserError, "Cannot convert SteamID \"#{steam_id}\" to a community ID."
46
165
  elsif steam_id.match(/^STEAM_[0-1]:[0-1]:[0-9]+$/).nil?
47
- raise SteamCondenserException.new("SteamID \"#{steam_id}\" doesn't have the correct format.")
166
+ raise SteamCondenserError, "SteamID \"#{steam_id}\" doesn't have the correct format."
48
167
  end
49
168
 
50
169
  steam_id = steam_id[6..-1].split(':').map!{|s| s.to_i}
@@ -52,15 +171,26 @@ class SteamId
52
171
  steam_id[1] + steam_id[2] * 2 + 76561197960265728
53
172
  end
54
173
 
55
- # Creates a new SteamId object using the SteamID64 converted from a server
56
- # SteamID given by +steam_id+
174
+ # Creates a new `SteamId` instance using a SteamID as used on servers
175
+ #
176
+ # The SteamID from the server is converted into a 64bit numeric SteamID first
177
+ # before this is used to retrieve the corresponding Steam Community profile.
178
+ #
179
+ # @param [String] steam_id The SteamID string as used on servers, like
180
+ # `STEAM_0:0:12345`
181
+ # @return [SteamId] The `SteamId` belonging to the given SteamID
182
+ # @see .convert_steam_id_to_community_id
183
+ # @see #initialize
57
184
  def self.from_steam_id(steam_id)
58
185
  new(convert_steam_id_to_community_id(steam_id))
59
186
  end
60
187
 
61
- # Creates a new SteamId object for the given SteamID +id+, either numeric or
62
- # the custom URL specified by the user. If +fetch+ is +true+ (default),
63
- # fetch_data is used to load data into the object.
188
+ # Creates a new `SteamId` instance for the given Steam ID
189
+ #
190
+ # @param [String, Fixnum] id The custom URL of the Steam ID specified by the
191
+ # user or the 64bit SteamID
192
+ # @param [Boolean] fetch if `true` the Steam ID's data is loaded into the
193
+ # object
64
194
  def initialize(id, fetch = true)
65
195
  begin
66
196
  if id.is_a? Numeric
@@ -71,11 +201,15 @@ class SteamId
71
201
 
72
202
  super(fetch)
73
203
  rescue REXML::ParseException
74
- raise SteamCondenserException.new('SteamID could not be loaded.')
204
+ raise SteamCondenserError, 'SteamID could not be loaded.'
75
205
  end
76
206
  end
77
207
 
78
- # Returns the base URL for this SteamID
208
+ # Returns the base URL for this Steam ID
209
+ #
210
+ # This URL is different for Steam IDs having a custom URL.
211
+ #
212
+ # @return [String] The base URL for this SteamID
79
213
  def base_url
80
214
  if @custom_url.nil?
81
215
  "http://steamcommunity.com/profiles/#{@steam_id64}"
@@ -85,21 +219,25 @@ class SteamId
85
219
  end
86
220
 
87
221
  # Fetchs data from the Steam Community by querying the XML version of the
88
- # profile specified by the ID of this SteamID
222
+ # profile specified by the ID of this Steam ID
223
+ #
224
+ # @raise SteamCondenserError if the Steam ID data is not available, e.g.
225
+ # when it is private
226
+ # @see Cacheable#fetch
89
227
  def fetch
90
228
  profile_url = open(base_url + '?xml=1', {:proxy => true})
91
229
  profile = REXML::Document.new(profile_url.read).root
92
230
 
93
231
  unless REXML::XPath.first(profile, 'error').nil?
94
- raise SteamCondenserException.new(profile.elements['error'].text)
232
+ raise SteamCondenserError, profile.elements['error'].text
95
233
  end
96
234
 
97
- @nickname = profile.elements['steamID'].text
235
+ @nickname = CGI.unescapeHTML profile.elements['steamID'].text
98
236
  @steam_id64 = profile.elements['steamID64'].text.to_i
99
237
  @vac_banned = (profile.elements['vacBanned'].text == 1)
100
238
 
101
239
  unless REXML::XPath.first(profile, 'privacyMessage').nil?
102
- raise SteamCondenserException.new(profile.elements['privacyMessage'].text)
240
+ raise SteamCondenserError, profile.elements['privacyMessage'].text
103
241
  end
104
242
 
105
243
  @image_url = profile.elements['avatarIcon'].text[0..-5]
@@ -108,28 +246,23 @@ class SteamId
108
246
  @state_message = profile.elements['stateMessage'].text
109
247
  @visibility_state = profile.elements['visibilityState'].text.to_i
110
248
 
111
- # Only public profiles can be scanned for further information
112
249
  if @privacy_state == 'public'
113
250
  @custom_url = profile.elements['customURL'].text.downcase
114
251
  @custom_url = nil if @custom_url.empty?
115
252
 
116
- # The favorite game cannot be set since 10/10/2008, but old profiles
117
- # still have this. May be removed in a future version.
118
253
  unless REXML::XPath.first(profile, 'favoriteGame').nil?
119
254
  @favorite_game = profile.elements['favoriteGame/name'].text
120
255
  @favorite_game_hours_played = profile.elements['favoriteGame/hoursPlayed2wk'].text
121
256
  end
122
257
 
123
- @head_line = profile.elements['headline'].text
124
- @hours_played = profile.elements['hoursPlayed2Wk'].text.to_f
125
- @location = profile.elements['location'].text
126
- @member_since = Time.parse(profile.elements['memberSince'].text)
127
- @real_name = profile.elements['realname'].text
128
- @steam_rating = profile.elements['steamRating'].text.to_f
129
- @summary = profile.elements['summary'].text
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
130
265
 
131
- # The most played games only exist if a user played at least one game in
132
- # the last two weeks
133
266
  @most_played_games = {}
134
267
  unless REXML::XPath.first(profile, 'mostPlayedGames').nil?
135
268
  profile.elements.each('mostPlayedGames/mostPlayedGame') do |most_played_game|
@@ -147,7 +280,7 @@ class SteamId
147
280
  @links = {}
148
281
  unless REXML::XPath.first(profile, 'mostPlayedGames').nil?
149
282
  profile.elements.each('weblinks/weblink') do |link|
150
- @links[link.elements['title'].text] = link.elements['link'].text
283
+ @links[CGI.unescapeHTML link.elements['title'].text] = link.elements['link'].text
151
284
  end
152
285
  end
153
286
  end
@@ -156,6 +289,12 @@ class SteamId
156
289
  end
157
290
 
158
291
  # Fetches the friends of this user
292
+ #
293
+ # This creates a new `SteamId` instance for each of the friends without
294
+ # fetching their data.
295
+ #
296
+ # @see #friends
297
+ # @see #initialize
159
298
  def fetch_friends
160
299
  url = "#{base_url}/friends?xml=1"
161
300
 
@@ -167,80 +306,172 @@ class SteamId
167
306
  end
168
307
 
169
308
  # Fetches the games this user owns
309
+ #
310
+ # This fills the game hash with the names of the games as keys. The values
311
+ # will either be `false` if the game does not have stats or the game's
312
+ # "friendly name".
313
+ #
314
+ # @see #games
170
315
  def fetch_games
171
316
  url = "#{base_url}/games?xml=1"
172
317
 
173
- @games = {}
318
+ @games = {}
319
+ @playtimes = {}
174
320
  games_data = REXML::Document.new(open(url, {:proxy => true}).read).root
175
- games_data.elements.each('games/game') do |game|
176
- game_name = game.elements['name'].text
177
- if game.elements['globalStatsLink'].nil?
178
- @games[game_name] = false
179
- else
180
- friendly_name = game.elements['globalStatsLink'].text.match(/http:\/\/steamcommunity.com\/stats\/([^?\/]+)\/achievements\//)[1]
181
- @games[game_name] = friendly_name.downcase
321
+ games_data.elements.each('games/game') do |game_data|
322
+ game = SteamGame.new(game_data)
323
+ @games[game.app_id] = game
324
+ recent = total = 0
325
+ unless game_data.elements['hoursLast2Weeks'].nil?
326
+ recent = game_data.elements['hoursLast2Weeks'].text.to_f
327
+ end
328
+ unless game_data.elements['hoursOnRecord'].nil?
329
+ total = game_data.elements['hoursOnRecord'].text.to_f
182
330
  end
331
+ @playtimes[game.app_id] = [(recent * 60).to_i, (total * 60).to_i]
183
332
  end
184
333
 
185
334
  true
186
335
  end
187
336
 
188
337
  # Returns the URL of the full-sized version of this user's avatar
338
+ #
339
+ # @return [String] The URL of the full-sized avatar
189
340
  def full_avatar_url
190
341
  "#{@image_url}_full.jpg"
191
342
  end
192
343
 
193
- # Returns a GameStats object for the given game for the owner of this SteamID
194
- def game_stats(game_name)
195
- if games.has_value? game_name
196
- friendly_name = game_name
197
- elsif games.has_key? game_name.downcase
198
- friendly_name = games[game_name.downcase]
199
- else
200
- raise ArgumentError.new("Stats for game #{game_name} do not exist.")
344
+ # Returns the stats for the given game for the owner of this SteamID
345
+ #
346
+ # @param [Fixnum, String] id The full or short name or the application ID of
347
+ # the game stats should be fetched for
348
+ # @return [GameStats] The statistics for the game with the given name
349
+ # @raise [SteamCondenserError] if the user does not own this game or it
350
+ # does not have any stats
351
+ # @see find_game
352
+ # @see SteamGame#has_stats?
353
+ def game_stats(id)
354
+ game = find_game id
355
+
356
+ unless game.has_stats?
357
+ raise SteamCondenserError, "\"#{game.name}\" does not have stats."
201
358
  end
202
359
 
203
- GameStats.create_game_stats(@custom_url || @steam_id64, friendly_name)
360
+ GameStats.create_game_stats(@custom_url || @steam_id64, game.short_name)
204
361
  end
205
362
 
206
- # Returns an Array of SteamId representing all Steam Community friends of this
207
- # user.
363
+ # Returns the Steam Community friends of this user
364
+ #
365
+ # If the friends haven't been fetched yet, this is done now.
366
+ #
367
+ # @return [Array<SteamId>] The friends of this user
368
+ # @see #fetch_friends
208
369
  def friends
209
370
  fetch_friends if @friends.nil?
210
371
  @friends
211
372
  end
212
373
 
213
- # Returns a Hash with the games this user owns. The keys are the games' names
214
- # and the values are the "friendly names" used for stats or +false+ if the
215
- # games has no stats.
374
+ # Returns the games this user owns
375
+ #
376
+ # The keys of the hash are the games' application IDs and the values are
377
+ # the corresponding game instances.
378
+ #
379
+ # If the friends haven't been fetched yet, this is done now.
380
+ #
381
+ # @return [Hash<Fixnum, SteamGame>] The games this user owns
382
+ # @see #fetch_games
216
383
  def games
217
384
  fetch_games if @games.nil?
218
385
  @games
219
386
  end
220
387
 
221
388
  # Returns the URL of the icon version of this user's avatar
389
+ #
390
+ # @return [String] The URL of the icon-sized avatar
222
391
  def icon_url
223
392
  "#{@image_url}.jpg"
224
393
  end
225
394
 
226
395
  # Returns whether the owner of this SteamID is VAC banned
396
+ #
397
+ # @return [Boolean] `true` if the user has been banned by VAC
227
398
  def is_banned?
228
399
  @vac_banned
229
400
  end
230
401
 
231
402
  # Returns whether the owner of this SteamId is playing a game
403
+ #
404
+ # @return [Boolean] `true` if the user is in-game
232
405
  def is_in_game?
233
406
  @online_state == 'in-game'
234
407
  end
235
408
 
236
409
  # Returns whether the owner of this SteamID is currently logged into Steam
410
+ #
411
+ # @return [Boolean] `true` if the user is online
237
412
  def is_online?
238
413
  @online_state != 'offline'
239
414
  end
240
415
 
241
416
  # Returns the URL of the medium-sized version of this user's avatar
417
+ #
418
+ # @return [String] The URL of the medium-sized avatar
242
419
  def medium_avatar_url
243
420
  "#{@image_url}_medium.jpg"
244
421
  end
245
422
 
423
+ # Returns the time in minutes this user has played this game in the last two
424
+ # weeks
425
+ #
426
+ # @param [Fixnum, String] id The full or short name or the application ID of
427
+ # the game
428
+ # @return [Fixnum] The number of minutes this user played the given game in
429
+ # the last two weeks
430
+ def recent_playtime(id)
431
+ game = find_game id
432
+ @playtimes[game.app_id][0]
433
+ end
434
+
435
+ # Returns the total time in minutes this user has played this game
436
+ #
437
+ # @param [Fixnum, String] id The full or short name or the application ID of
438
+ # the game
439
+ # @return [Fixnum] The total number of minutes this user played the given
440
+ # game
441
+ def total_playtime(id)
442
+ game = find_game id
443
+ @playtimes[game.app_id][1]
444
+ end
445
+
446
+ private
447
+
448
+ # Tries to find a game instance with the given application ID or full name or
449
+ # short name
450
+ #
451
+ # @param [Fixnum, String] id The full or short name or the application ID of
452
+ # the game
453
+ # @raise [SteamCondenserError] if the user does not own the game or no
454
+ # game with the given ID exists
455
+ # @return [SteamGame] The game found with the given ID
456
+ def find_game(id)
457
+ if id.is_a? Integer
458
+ game = games[id]
459
+ else
460
+ game = games.values.find do |game|
461
+ game.short_name == id || game.name == id
462
+ end
463
+ end
464
+
465
+ if game.nil?
466
+ if id.is_a? Numeric
467
+ message = "This SteamID does not own a game with application ID #{id}."
468
+ else
469
+ message = "This SteamID does not own the game \"#{id}\"."
470
+ end
471
+ raise SteamCondenserError, message
472
+ end
473
+
474
+ game
475
+ end
476
+
246
477
  end