steam-condenser 0.8.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 (72) hide show
  1. data/LICENSE +25 -0
  2. data/README.md +30 -0
  3. data/Rakefile +42 -0
  4. data/lib/abstract_class.rb +40 -0
  5. data/lib/byte_buffer.rb +126 -0
  6. data/lib/datagram_channel.rb +70 -0
  7. data/lib/exceptions/buffer_underflow_exception.rb +10 -0
  8. data/lib/exceptions/packet_format_exception.rb +12 -0
  9. data/lib/exceptions/rcon_no_auth_exception.rb +14 -0
  10. data/lib/exceptions/steam_condenser_exception.rb +8 -0
  11. data/lib/exceptions/timeout_exception.rb +10 -0
  12. data/lib/socket_channel.rb +54 -0
  13. data/lib/steam-condenser.rb +3 -0
  14. data/lib/steam/community/dods/dods_class.rb +32 -0
  15. data/lib/steam/community/dods/dods_stats.rb +47 -0
  16. data/lib/steam/community/dods/dods_weapon.rb +54 -0
  17. data/lib/steam/community/game_achievement.rb +26 -0
  18. data/lib/steam/community/game_class.rb +15 -0
  19. data/lib/steam/community/game_stats.rb +92 -0
  20. data/lib/steam/community/game_weapon.rb +29 -0
  21. data/lib/steam/community/l4d/l4d_explosive.rb +23 -0
  22. data/lib/steam/community/l4d/l4d_map.rb +34 -0
  23. data/lib/steam/community/l4d/l4d_stats.rb +166 -0
  24. data/lib/steam/community/l4d/l4d_weapon.rb +23 -0
  25. data/lib/steam/community/steam_group.rb +16 -0
  26. data/lib/steam/community/steam_id.rb +212 -0
  27. data/lib/steam/community/tf2/tf2_class.rb +31 -0
  28. data/lib/steam/community/tf2/tf2_class_factory.rb +38 -0
  29. data/lib/steam/community/tf2/tf2_engineer.rb +25 -0
  30. data/lib/steam/community/tf2/tf2_medic.rb +22 -0
  31. data/lib/steam/community/tf2/tf2_sniper.rb +20 -0
  32. data/lib/steam/community/tf2/tf2_spy.rb +23 -0
  33. data/lib/steam/community/tf2/tf2_stats.rb +39 -0
  34. data/lib/steam/packets/a2a_ack_packet.rb +21 -0
  35. data/lib/steam/packets/a2a_ping_packet.rb +18 -0
  36. data/lib/steam/packets/a2m_get_servers_batch2_packet.rb +25 -0
  37. data/lib/steam/packets/a2s_info_packet.rb +18 -0
  38. data/lib/steam/packets/a2s_player_packet.rb +22 -0
  39. data/lib/steam/packets/a2s_rules_packet.rb +21 -0
  40. data/lib/steam/packets/a2s_serverquery_getchallenge_packet.rb +18 -0
  41. data/lib/steam/packets/m2a_server_batch_packet.rb +37 -0
  42. data/lib/steam/packets/rcon/rcon_auth_request.rb +16 -0
  43. data/lib/steam/packets/rcon/rcon_auth_response.rb +16 -0
  44. data/lib/steam/packets/rcon/rcon_exec_request.rb +16 -0
  45. data/lib/steam/packets/rcon/rcon_exec_response.rb +20 -0
  46. data/lib/steam/packets/rcon/rcon_goldsrc_request.rb +20 -0
  47. data/lib/steam/packets/rcon/rcon_goldsrc_response.rb +20 -0
  48. data/lib/steam/packets/rcon/rcon_packet.rb +36 -0
  49. data/lib/steam/packets/rcon/rcon_packet_factory.rb +37 -0
  50. data/lib/steam/packets/request_with_challenge.rb +16 -0
  51. data/lib/steam/packets/s2a_info2_packet.rb +48 -0
  52. data/lib/steam/packets/s2a_info_base_packet.rb +29 -0
  53. data/lib/steam/packets/s2a_info_detailed_packet.rb +50 -0
  54. data/lib/steam/packets/s2a_player_packet.rb +32 -0
  55. data/lib/steam/packets/s2a_rules_packet.rb +33 -0
  56. data/lib/steam/packets/s2c_challenge_packet.rb +23 -0
  57. data/lib/steam/packets/steam_packet.rb +48 -0
  58. data/lib/steam/packets/steam_packet_factory.rb +90 -0
  59. data/lib/steam/servers/game_server.rb +222 -0
  60. data/lib/steam/servers/goldsrc_server.rb +36 -0
  61. data/lib/steam/servers/master_server.rb +60 -0
  62. data/lib/steam/servers/source_server.rb +65 -0
  63. data/lib/steam/sockets/goldsrc_socket.rb +108 -0
  64. data/lib/steam/sockets/master_server_socket.rb +20 -0
  65. data/lib/steam/sockets/rcon_socket.rb +50 -0
  66. data/lib/steam/sockets/source_socket.rb +72 -0
  67. data/lib/steam/sockets/steam_socket.rb +63 -0
  68. data/lib/steam/steam_player.rb +51 -0
  69. data/test/query_tests.rb +71 -0
  70. data/test/rcon_tests.rb +79 -0
  71. data/test/steam_community_tests.rb +36 -0
  72. metadata +132 -0
@@ -0,0 +1,23 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2009, Sebastian Staudt
5
+
6
+ require 'steam/community/game_weapon'
7
+
8
+ class L4DWeapon < GameWeapon
9
+
10
+ attr_reader :accuracy, :headshots_percentage, :kill_percentage
11
+
12
+ # Creates a new instance of L4DWeapon based on the assigned XML data
13
+ def initialize(weapon_data)
14
+ super weapon_data
15
+
16
+ @accuracy = weapon_data.elements['accuracy'].text
17
+ @headshots_percentage = weapon_data.elements['headshots'].text
18
+ @id = weapon_data.name
19
+ @kill_percentage = weapon_data.elements['killpct'].text
20
+ @shots = weapon_data.elements['shots'].text.to_i
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ # The SteamGroup class represents a group in the Steam Community
9
+ class SteamGroup
10
+
11
+ # Creates a SteamGroup object with the given group ID
12
+ def initialize(id)
13
+ @id = id
14
+ end
15
+
16
+ end
@@ -0,0 +1,212 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require "open-uri"
7
+ require "rexml/document"
8
+
9
+ require "exceptions/steam_condenser_exception"
10
+ require "steam/community/game_stats"
11
+ require "steam/community/steam_group"
12
+
13
+ # The SteamId class represents a Steam Community profile (also called Steam ID)
14
+ class SteamId
15
+
16
+ attr_reader :custom_url, :favorite_game, :favorite_game_hours_played,
17
+ :friends, :groups, :head_line, :hours_played, :image_url, :links,
18
+ :location, :member_since, :most_played_games, :privacy_state,
19
+ :real_name, :state_message, :steam_rating, :steam_rating_text,
20
+ :summary, :vac_banned, :visibility_state
21
+
22
+ # Converts a SteamID as reported by game servers to a 64bit SteamID
23
+ def self.convert_steam_id_to_community_id(steam_id)
24
+ if steam_id == "STEAM_ID_LAN"
25
+ raise SteamCondenserException.new("Cannot convert SteamID \"STEAM_ID_LAN\" to a community ID.")
26
+ elsif steam_id.match(/STEAM_[0-1]:[0-1]:[0-9]+/).nil?
27
+ raise SteamCondenserException.new("SteamID \"#{steam_id}\" doesn't have the correct format.")
28
+ end
29
+
30
+ steam_id = steam_id[6..-1].split(":").map!{|s| s.to_i}
31
+
32
+ return steam_id[1] + 76561197960265728 + steam_id[2] * 2
33
+ end
34
+
35
+ # Creates a new SteamId object using a SteamID64 converted from a server
36
+ # SteamID
37
+ def self.get_from_steam_id(steam_id)
38
+ return self.new(self.convert_steam_id_to_community_id(steam_id))
39
+ end
40
+
41
+ # Creates a new SteamId object for the given SteamID, either numeric or the
42
+ # custom URL specified by the user. If +fetch+ is +true+ (default), fetch_data
43
+ # is used to load data into the object.
44
+ def initialize(id, fetch = true)
45
+ if id.is_a? Numeric
46
+ @steam_id64 = id
47
+ else
48
+ @custom_url = id
49
+ end
50
+
51
+ begin
52
+ self.fetch_data if fetch
53
+ rescue REXML::ParseException
54
+ raise SteamCondenserException.new("SteamID could not be loaded.")
55
+ end
56
+ end
57
+
58
+ # Returns the base URL for this SteamID
59
+ def base_url
60
+ if @custom_url.nil?
61
+ "http://steamcommunity.com/profiles/#{@steam_id64}"
62
+ else
63
+ "http://steamcommunity.com/id/#{@custom_url}"
64
+ end
65
+ end
66
+
67
+ # Fetchs data from the Steam Community by querying the XML version of the
68
+ # profile specified by the ID of this SteamID
69
+ def fetch_data
70
+ url = base_url << '?xml=1'
71
+
72
+ profile_url = open(url, {:proxy => true})
73
+ if profile_url.base_uri.to_s != url
74
+ profile_url = open(profile_url.base_uri.to_s + "?xml=1", {:proxy => true})
75
+ end
76
+
77
+ profile = REXML::Document.new(profile_url.read).elements["profile"]
78
+
79
+ @image_url = profile.elements["avatarIcon"].text[0..-5]
80
+ @online_state = profile.elements["onlineState"].text
81
+ @privacy_state = profile.elements["privacyState"].text
82
+ @state_message = profile.elements["stateMessage"].text
83
+ @steam_id = profile.elements["steamID"].text
84
+ @steam_id64 = profile.elements["steamID64"].text.to_i
85
+ @vac_banned = (profile.elements["vacBanned"].text == 1)
86
+ @visibility_state = profile.elements["visibilityState"].text.to_i
87
+
88
+ # Only public profiles can be scanned for further information
89
+ if @privacy_state == "public"
90
+ @custom_url = profile.elements["customURL"].text
91
+
92
+ unless REXML::XPath.first(profile, "favoriteGame").nil?
93
+ @favorite_game = profile.elements["favoriteGame/name"].text
94
+ @favorite_game_hours_played = profile.elements["favoriteGame/hoursPlayed2wk"].text
95
+ end
96
+
97
+ @head_line = profile.elements["headline"].text
98
+ @hours_played = profile.elements["hoursPlayed2Wk"].text.to_f
99
+ @location = profile.elements["location"].text
100
+ @member_since = Time.parse profile.elements["memberSince"].text
101
+ @real_name = profile.elements["realname"].text
102
+ @steam_rating = profile.elements["steamRating"].text.to_f
103
+ @summary = profile.elements["summary"].text
104
+
105
+ unless REXML::XPath.first(profile, "mostPlayedGames").nil?
106
+ @most_played_games = Hash.new
107
+ profile.elements["mostPlayedGames"].elements.each("mostPlayedGame") do |most_played_game|
108
+ @most_played_games[most_played_game.elements["gameName"].text] = most_played_game.elements["hoursPlayed"].text.to_f
109
+ end
110
+ end
111
+
112
+ @friends = Array.new
113
+ profile.elements["friends"].elements.each("friend") do |friend|
114
+ @friends << SteamId.new(friend.elements["steamID64"].text.to_i, false)
115
+ end
116
+
117
+ @groups = Array.new
118
+ profile.elements["groups"].elements.each("group") do |group|
119
+ @groups << SteamGroup.new(group.elements["groupID64"].text.to_i)
120
+ end
121
+
122
+ @links = Hash.new
123
+ profile.elements["weblinks"].elements.each("weblink") do |link|
124
+ @links[link.elements["title"].text] = link.elements["link"].text
125
+ end
126
+ end
127
+ end
128
+
129
+ # Fetches the games this user owns
130
+ def fetch_games
131
+ require 'rubygems'
132
+ require 'Hpricot'
133
+
134
+ url = base_url << '/games'
135
+
136
+ @games = {}
137
+ games_data = Hpricot(open(url).read).at('div#mainContents')
138
+
139
+ games_data.traverse_some_element('h4') do |game|
140
+ game_name = game.inner_html
141
+ stats = game.next_sibling
142
+ if stats.name == 'br'
143
+ @games[game_name] = false
144
+ else
145
+ stats = stats.next_sibling if stats.name == 'h5'
146
+ if stats.name == 'br'
147
+ @games[game_name] = false
148
+ else
149
+ stats = stats.siblings_at(2)[0]
150
+ friendly_name = stats['href'].match(/http:\/\/steamcommunity.com\/stats\/([0-9a-zA-Z:]+)\/achievements\//)[1]
151
+ @games[game_name] = friendly_name.downcase
152
+ end
153
+ end
154
+ end
155
+
156
+ return true
157
+ end
158
+
159
+ # Returns the URL of the full version of this user's avatar
160
+ def full_avatar_url
161
+ return "#{@image_url}_full.jpg"
162
+ end
163
+
164
+ # Returns a GameStats object for the given game for the owner of this SteamID
165
+ def game_stats(game_name)
166
+ game_name.downcase!
167
+
168
+ if games.has_value? game_name
169
+ friendly_name = game_name
170
+ elsif games.has_key? game_name
171
+ friendly_name = games[game_name]
172
+ else
173
+ raise ArgumentError.new("Stats for game #{game_name} do not exist.")
174
+ end
175
+
176
+ GameStats.create_game_stats(@custom_url || @steam_id64, friendly_name)
177
+ end
178
+
179
+ # Returns a Hash with the games this user owns. The keys are the games' names
180
+ # and the values are the "friendly names" used for stats or +false+ if the
181
+ # games has no stats.
182
+ def games
183
+ fetch_games if @games.nil?
184
+ @games
185
+ end
186
+
187
+ # Returns the URL of the icon version of this user's avatar
188
+ def icon_url
189
+ return "#{@image_url}.jpg"
190
+ end
191
+
192
+ # Returns whether the owner of this SteamID is VAC banned
193
+ def is_banned?
194
+ return @vac_banned
195
+ end
196
+
197
+ # Returns whether the owner of this SteamId is playing a game
198
+ def is_in_game?
199
+ return @online_state == "in-game"
200
+ end
201
+
202
+ # Returns whether the owner of this SteamID is currently logged into Steam
203
+ def is_online?
204
+ return @online_state != "offline"
205
+ end
206
+
207
+ # Returns the URL of the medium version of this user's avatar
208
+ def medium_avatar_url
209
+ return "#{@image_url}_medium.jpg"
210
+ end
211
+
212
+ end
@@ -0,0 +1,31 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require 'steam/community/game_class'
7
+
8
+ # Represents the stats for a Team Fortress 2 class for a specific user
9
+ class TF2Class < GameClass
10
+
11
+ attr_reader :max_buildings_destroyed, :max_captures, :max_damage,
12
+ :max_defenses, :max_dominations, :max_kill_assists, :max_kills,
13
+ :max_revenges, :max_score, :max_time_alive
14
+
15
+ # Creates a new instance of TF2Class based on the assigned XML data
16
+ def initialize(class_data)
17
+ @name = class_data.elements["className"].text
18
+ @max_buildings_destroyed = class_data.elements["ibuildingsdestroyed"].text.to_i
19
+ @max_captures = class_data.elements["ipointcaptures"].text.to_i
20
+ @max_damage = class_data.elements["idamagedealt"].text.to_i
21
+ @max_defenses = class_data.elements["ipointdefenses"].text.to_i
22
+ @max_dominations = class_data.elements["idominations"].text.to_i
23
+ @max_kill_assists = class_data.elements["ikillassists"].text.to_i
24
+ @max_kills = class_data.elements["inumberofkills"].text.to_i
25
+ @max_revenges = class_data.elements["irevenge"].text.to_i
26
+ @max_score = class_data.elements["ipointsscored"].text.to_i
27
+ @max_time_alive = class_data.elements["iplaytime"].text.to_i
28
+ @play_time = class_data.elements["playtimeSeconds"].text.to_i
29
+ end
30
+
31
+ end
@@ -0,0 +1,38 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "abstract_class"
9
+ require "steam/community/tf2/tf2_class"
10
+ require "steam/community/tf2/tf2_engineer"
11
+ require "steam/community/tf2/tf2_medic"
12
+ require "steam/community/tf2/tf2_sniper"
13
+ require "steam/community/tf2/tf2_spy"
14
+
15
+ # The TF2ClassFactory is used to created instances of TF2Class based on the XML
16
+ # input data
17
+ class TF2ClassFactory
18
+
19
+ include AbstractClass
20
+
21
+ # Creates a new instance of TF2Class storing the statistics for a Team
22
+ # Fortress 2 class with the assigned XML data
23
+ def self.get_tf2_class(class_data)
24
+ case class_data.elements("className")[0].text
25
+ when "Engineer" then
26
+ return TF2Engineer.new(class_data)
27
+ when "Medic" then
28
+ return TF2Medic.new(class_data)
29
+ when "Sniper" then
30
+ return TF2Sniper.new(class_data)
31
+ when "Spy" then
32
+ return TF2Spy.new(class_data)
33
+ else
34
+ return TF2Class.new(class_data)
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,25 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "steam/community/tf2/tf2_class"
9
+
10
+ # Represents the stats for the Team Fortress 2 engineer class for a specific
11
+ # user
12
+ class TF2Engineer < TF2Class
13
+
14
+ attr_reader :max_buildings_built, :max_teleports, :max_sentry_kills
15
+
16
+ # Creates a new instance of TF2Engineer based on the assigned XML data
17
+ def initialize(class_data)
18
+ super class_data
19
+
20
+ @max_buildings_built = class_data.elements["ibuildingsbuilt"].text.to_i
21
+ @max_teleports = class_data.elements["inumteleports"].text.to_i
22
+ @max_sentry_kills = class_data.elements["isentrykills"].text.to_i
23
+ end
24
+
25
+ end
@@ -0,0 +1,22 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "steam/community/tf2/tf2_class"
9
+
10
+ # Represents the stats for the Team Fortress 2 medic class for a specific user
11
+ class TF2Medic < TF2Class
12
+
13
+ attr_reader :max_health_healed, :max_ubercharges
14
+
15
+ def initialize(class_data)
16
+ super class_data
17
+
18
+ @max_health_healed = class_data.elements["ihealthpointshealed"].text.to_i
19
+ @max_ubercharges = class_data.elements["inuminvulnerable"].text.to_i
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "steam/community/tf2/tf2_class"
9
+
10
+ # Represents the stats for the Team Fortress 2 sniper class for a specific user
11
+ class TF2Sniper < TF2Class
12
+
13
+ # Creates a new instance of TF2Sniper based on the assigned XML data
14
+ def initialize(class_data)
15
+ super class_data
16
+
17
+ @max_headshots = class_data.elements["iheadshots"].text.to_i
18
+ end
19
+
20
+ end
@@ -0,0 +1,23 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "steam/community/tf2/tf2_class"
9
+
10
+ # Represents the stats for the Team Fortress 2 spy class for a specific user
11
+ class TF2Spy < TF2Class
12
+
13
+ attr_reader :max_backstabs, :max_health_leeched
14
+
15
+ # Creates a new instance of TF2Spy based on the assigned XML data
16
+ def initialize(class_data)
17
+ super class_data
18
+
19
+ @max_backstabs = class_data.elements["ibackstabs"].text.to_i
20
+ @max_health_leeched = class_data.elements["ihealthpointsleached"].text.to_i
21
+ end
22
+
23
+ end
@@ -0,0 +1,39 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require "steam/community/tf2/tf2_class"
7
+
8
+ # The TF2Stats class represents the game statistics for a single user in Team
9
+ # Fortress 2
10
+ class TF2Stats < GameStats
11
+
12
+ attr_reader :accumulated_points
13
+
14
+ # Creates a TF2Stats object by calling the super constructor with the game
15
+ # name "TF2"
16
+ def initialize(steam_id)
17
+ super steam_id, "TF2"
18
+
19
+ if public?
20
+ @accumulated_points = @xml_data.elements["stats"].elements["accumulatedPoints"].text.to_i
21
+ end
22
+ end
23
+
24
+ # Returns a Hash of TF2Class for this user containing all Team Fortress 2
25
+ # classes. If the classes haven't been parsed already, parsing is done now.
26
+ def class_stats
27
+ return unless public?
28
+
29
+ if @class_stats.nil?
30
+ @class_stats = Hash.new
31
+ @xml_data.elements["stats"].elements.each("classData") do |class_data|
32
+ @class_stats[class_data.elements["className"].text] = TF2Class.new class_data
33
+ end
34
+ end
35
+
36
+ return @class_stats
37
+ end
38
+
39
+ end