steam-condenser 0.13.1 → 0.14.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 (58) hide show
  1. data/lib/exceptions/packet_format_exception.rb +12 -5
  2. data/lib/exceptions/rcon_ban_exception.rb +11 -3
  3. data/lib/exceptions/rcon_no_auth_exception.rb +10 -3
  4. data/lib/exceptions/steam_condenser_exception.rb +8 -5
  5. data/lib/exceptions/timeout_exception.rb +13 -3
  6. data/lib/exceptions/web_api_exception.rb +31 -23
  7. data/lib/steam-condenser.rb +9 -3
  8. data/lib/steam-condenser/community.rb +8 -1
  9. data/lib/steam-condenser/servers.rb +7 -1
  10. data/lib/steam-condenser/version.rb +2 -1
  11. data/lib/steam/community/cacheable.rb +4 -4
  12. data/lib/steam/community/game_inventory.rb +119 -0
  13. data/lib/steam/community/game_item.rb +38 -0
  14. data/lib/steam/community/game_stats.rb +10 -4
  15. data/lib/steam/community/portal2/portal2_inventory.rb +21 -0
  16. data/lib/steam/community/portal2/portal2_item.rb +35 -0
  17. data/lib/steam/community/portal2/portal2_stats.rb +28 -0
  18. data/lib/steam/community/tf2/tf2_inventory.rb +7 -37
  19. data/lib/steam/community/tf2/tf2_item.rb +8 -79
  20. data/lib/steam/community/tf2/tf2_stats.rb +10 -13
  21. data/lib/steam/community/web_api.rb +1 -0
  22. data/lib/steam/packets/a2m_get_servers_batch2_packet.rb +37 -4
  23. data/lib/steam/packets/a2s_info_packet.rb +10 -5
  24. data/lib/steam/packets/a2s_player_packet.rb +16 -6
  25. data/lib/steam/packets/a2s_rules_packet.rb +16 -4
  26. data/lib/steam/packets/a2s_serverquery_getchallenge_packet.rb +11 -5
  27. data/lib/steam/packets/c2m_checkmd5_packet.rb +10 -4
  28. data/lib/steam/packets/m2a_server_batch_packet.rb +19 -3
  29. data/lib/steam/packets/m2c_isvalidmd5_packet.rb +14 -5
  30. data/lib/steam/packets/m2s_requestrestart_packet.rb +14 -4
  31. data/lib/steam/packets/rcon/rcon_auth_request.rb +15 -3
  32. data/lib/steam/packets/rcon/rcon_auth_response.rb +17 -3
  33. data/lib/steam/packets/rcon/rcon_exec_request.rb +15 -3
  34. data/lib/steam/packets/rcon/rcon_exec_response.rb +20 -3
  35. data/lib/steam/packets/rcon/rcon_goldsrc_request.rb +18 -3
  36. data/lib/steam/packets/rcon/rcon_goldsrc_response.rb +17 -3
  37. data/lib/steam/packets/rcon/rcon_packet.rb +31 -3
  38. data/lib/steam/packets/rcon/rcon_packet_factory.rb +15 -5
  39. data/lib/steam/packets/rcon/rcon_terminator.rb +14 -5
  40. data/lib/steam/packets/request_with_challenge.rb +10 -5
  41. data/lib/steam/packets/s2a_info2_packet.rb +14 -5
  42. data/lib/steam/packets/s2a_info_base_packet.rb +16 -6
  43. data/lib/steam/packets/s2a_info_detailed_packet.rb +15 -8
  44. data/lib/steam/packets/s2a_logstring_packet.rb +11 -5
  45. data/lib/steam/packets/s2a_player_packet.rb +14 -6
  46. data/lib/steam/packets/s2a_rules_packet.rb +15 -5
  47. data/lib/steam/packets/s2c_challenge_packet.rb +17 -6
  48. data/lib/steam/packets/s2m_heartbeat2_packet.rb +18 -4
  49. data/lib/steam/packets/steam_packet.rb +14 -5
  50. data/lib/steam/packets/steam_packet_factory.rb +25 -2
  51. data/lib/steam/servers/game_server.rb +154 -25
  52. data/lib/steam/servers/goldsrc_server.rb +35 -3
  53. data/lib/steam/servers/master_server.rb +77 -23
  54. data/lib/steam/servers/server.rb +42 -3
  55. data/lib/steam/servers/source_server.rb +35 -11
  56. data/lib/stringio_additions.rb +48 -3
  57. data/test/query_tests.rb +4 -4
  58. metadata +11 -6
@@ -1,15 +1,33 @@
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
6
  require 'steam/servers/game_server'
7
7
  require 'steam/sockets/goldsrc_socket'
8
8
 
9
+ # This class represents a GoldSrc game server and can be used to query
10
+ # information about and remotely execute commands via RCON on the server
11
+ #
12
+ # A GoldSrc game server is an instance of the Half-Life Dedicated Server (HLDS)
13
+ # running games using Valve's GoldSrc engine, like Half-Life Deathmatch,
14
+ # Counter-Strike 1.6 or Team Fortress Classic.
15
+ #
16
+ # @author Sebastian Staudt
17
+ # @see SourceServer
9
18
  class GoldSrcServer
10
19
 
11
20
  include GameServer
12
21
 
22
+ # Creates a new instance of a GoldSrc server object
23
+ #
24
+ # @param [String] address Either an IP address, a DNS name or one of them
25
+ # combined with the port number. If a port number is given, e.g.
26
+ # 'server.example.com:27016' it will override the second argument.
27
+ # @param [Fixnum] port The port the server is listening on
28
+ # @raise [SteamCondenserException] if an host name cannot be resolved
29
+ # @param [Boolean] is_hltv HLTV servers need special treatment, so this is
30
+ # used to determine if the server is a HLTV server
13
31
  def initialize(address, port = 27015, is_hltv = false)
14
32
  super address, port
15
33
 
@@ -17,15 +35,29 @@ class GoldSrcServer
17
35
  end
18
36
 
19
37
  # Initializes the socket to communicate with the GoldSrc server
38
+ #
39
+ # @see GoldSrcSocket
20
40
  def init_socket
21
41
  @socket = GoldSrcSocket.new @ip_address, @port, @is_hltv
22
42
  end
23
43
 
44
+ # Saves the password for authenticating the RCON communication with the
45
+ # server
46
+ #
47
+ # @param [String] password The RCON password of the server
48
+ # @return [true] GoldSrc's RCON does not preauthenticate connections so this
49
+ # method always returns `true`
50
+ # @see #rcon_exec
24
51
  def rcon_auth(password)
25
52
  @rcon_password = password
26
53
  true
27
54
  end
28
55
 
56
+ # Remotely executes a command on the server via RCON
57
+ #
58
+ # @param [String] command The command to execute on the server via RCON
59
+ # @return [String] The output of the executed command
60
+ # @see #rcon_auth
29
61
  def rcon_exec(command)
30
62
  @socket.rcon_exec(@rcon_password, command).strip
31
63
  end
@@ -1,5 +1,5 @@
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
4
  # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
@@ -9,35 +9,59 @@ require 'steam/packets/s2m_heartbeat2_packet'
9
9
  require 'steam/servers/server'
10
10
  require 'steam/sockets/master_server_socket'
11
11
 
12
+ # This class represents a Steam master server and can be used to get game
13
+ # servers which are publicly available
14
+ #
15
+ # An intance of this class can be used much like Steam's server browser to get
16
+ # a list of available game servers, including filters to narrow down the search
17
+ # results.
18
+ #
19
+ # @author Sebastian Staudt
12
20
  class MasterServer
13
21
 
14
22
  include Server
15
23
 
24
+ # The master server address to query for GoldSrc game servers
16
25
  GOLDSRC_MASTER_SERVER = 'hl1master.steampowered.com', 27010
26
+
27
+ # The master server address to query for GoldSrc game servers
17
28
  SOURCE_MASTER_SERVER = 'hl2master.steampowered.com', 27011
18
29
 
30
+ # The region code for the US east coast
19
31
  REGION_US_EAST_COAST = 0x00
32
+
33
+ # The region code for the US west coast
20
34
  REGION_US_WEST_COAST = 0x01
35
+
36
+ # The region code for South America
21
37
  REGION_SOUTH_AMERICA = 0x02
38
+
39
+ # The region code for Europe
22
40
  REGION_EUROPE = 0x03
41
+
42
+ # The region code for Asia
23
43
  REGION_ASIA = 0x04
44
+
45
+ # The region code for Australia
24
46
  REGION_AUSTRALIA = 0x05
47
+
48
+ # The region code for the Middle East
25
49
  REGION_MIDDLE_EAST = 0x06
26
- REGION_AFRICA = 0x07
27
- REGION_ALL = 0xFF
28
50
 
29
- # Creates a new instance of a master server object
30
- def initialize(address, port)
31
- super
51
+ # The region code for Africa
52
+ REGION_AFRICA = 0x07
32
53
 
33
- @server_array = []
34
- end
54
+ # The region code for the whole world
55
+ REGION_ALL = 0xFF
35
56
 
36
- # Request a challenge number from the master server. This is used for further
37
- # communication with the master server.
57
+ # Request a challenge number from the master server.
58
+ #
59
+ # This is used for further communication with the master server.
38
60
  #
39
- # Please note that this is NOT needed for finding servers using the +servers+
40
- # method
61
+ # @note Please note that this is **not** needed for finding servers using
62
+ # {#servers}.
63
+ # @return The challenge number from the master server
64
+ # @see #send_heartbeat
41
65
  def challenge
42
66
  failsafe do
43
67
  @socket.send C2M_CHECKMD5_Packet.new
@@ -46,19 +70,43 @@ class MasterServer
46
70
  end
47
71
 
48
72
  # Initializes the socket to communicate with the master server
73
+ #
74
+ # @see MasterServerSocket
49
75
  def init_socket
50
76
  @socket = MasterServerSocket.new @ip_address, @port
51
77
  end
52
78
 
79
+ # Returns a list of game server matching the given region and filters
80
+ #
81
+ # Filtering:
82
+ # Instead of filtering the results sent by the master server locally, you
83
+ # should at least use the following filters to narrow down the results sent by
84
+ # the master server.
85
+ #
86
+ # Available filters:
87
+ #
88
+ # * `\type\d`: Request only dedicated servers
89
+ # * `\secure\1`: Request only secure servers
90
+ # * `\gamedir\[mod]`: Request only servers of a specific mod
91
+ # * `\map\[mapname]`: Request only servers running a specific map
92
+ # * `\linux\1`: Request only linux servers
93
+ # * `\emtpy\1`: Request only **non**-empty servers
94
+ # * `\full\`: Request only servers **not** full
95
+ # * `\proxy\1`: Request only spectator proxy servers
96
+ #
97
+ # @note Receiving all servers from the master server is taking quite some
98
+ # time.
99
+ # @param [Fixnum] region_code The region code to specify a location of the
100
+ # game servers
101
+ # @param [String] filters The filters that game servers should match
102
+ # @return [Array<Array<String>>] A list of game servers matching the given
103
+ # region and filters
104
+ # @see A2M_GET_SERVERS_BATCH2_Packet
53
105
  def servers(region_code = MasterServer::REGION_ALL, filters = '')
54
- update_servers region_code, filters if @server_array.empty?
55
- @server_array
56
- end
57
-
58
- def update_servers(region_code, filters)
59
106
  fail_count = 0
60
107
  finished = false
61
108
  current_server = '0.0.0.0:0'
109
+ server_array = []
62
110
 
63
111
  failsafe do
64
112
  begin
@@ -70,7 +118,7 @@ class MasterServer
70
118
  finished = true
71
119
  else
72
120
  current_server = server
73
- @server_array << server.split(':')
121
+ server_array << server.split(':')
74
122
  end
75
123
  end
76
124
  fail_count = 0
@@ -79,16 +127,22 @@ class MasterServer
79
127
  end
80
128
  end while !finished
81
129
  end
130
+
131
+ server_array
82
132
  end
83
133
 
84
134
  # Sends a constructed heartbeat to the master server
85
135
  #
86
136
  # This can be used to check server versions externally.
87
137
  #
88
- # The reply from the master server usually zero or more packets. Zero means
89
- # either the heartbeat was accepted by the master or there was a timeout. So
90
- # usually it's best to repeat a heartbeat a few times when not receiving any
91
- # packets.
138
+ # @param [Hash<Symbol, Object>] The data to send with the heartbeat request
139
+ # @raise [SteamCondenserException] if heartbeat data is missing the
140
+ # challenge number or the reply cannot be parsed
141
+ # @return [Array<SteamPacket>] Zero or more reply packets from the server.
142
+ # Zero means either the heartbeat was accepted by the master or there
143
+ # was a timeout. So usually it's best to repeat a heartbeat a few
144
+ # times when not receiving any packets.
145
+ # @see S2M_HEARTBEAT2_Packet
92
146
  def send_heartbeat(data)
93
147
  failsafe do
94
148
  @socket.send S2M_HEARTBEAT2_Packet.new(data)
@@ -7,10 +7,30 @@ require 'socket'
7
7
 
8
8
  # This module is included by all classes implementing server functionality
9
9
  #
10
- # It provides basic name resolution features.
10
+ # It provides basic name resolution features and the ability to rotate between
11
+ # different IP addresses belonging to a single DNS name.
12
+ #
13
+ # @author Sebastian Staudt
11
14
  module Server
12
15
 
16
+ # Returns a list of host names associated with this server
17
+ #
18
+ # @return [Array<String>] The host names of this server
19
+ attr_reader :host_names
20
+
21
+ # Returns a list of IP addresses associated with this server
22
+ #
23
+ # @return [Array<String>] The IP addresses of this server
24
+ attr_reader :ip_addresses
25
+
13
26
  # Creates a new server instance with the given address and port
27
+ #
28
+ # @param [String] address Either an IP address, a DNS name or one of them
29
+ # combined with the port number. If a port number is given, e.g.
30
+ # 'server.example.com:27016' it will override the second argument.
31
+ # @param [Fixnum] port The port the server is listening on
32
+ # @see init_socket
33
+ # @raise [SteamCondenserException] if an host name cannot be resolved
14
34
  def initialize(address, port = nil)
15
35
  address = address.to_s
16
36
  address, port = address.split(':', 2) if address.include? ':'
@@ -33,8 +53,17 @@ module Server
33
53
 
34
54
  # Rotate this server's IP address to the next one in the IP list
35
55
  #
36
- # This method will return +true+, if the IP list reached its end. If the list
37
- # contains only one IP address, this method will instantly return +true+.
56
+ # If this method returns `true`, it indicates that all IP addresses have been
57
+ # used, hinting at the server(s) being unreachable. An appropriate action
58
+ # should be taken to inform the user.
59
+ #
60
+ # Servers with only one IP address will always cause this method to return
61
+ # `true` and the sockets will not be reinitialized.
62
+ #
63
+ # @return [Boolean] `true`, if the IP list reached its end. If the list
64
+ # contains only one IP address, this method will instantly return
65
+ # `true`
66
+ # @see #init_socket
38
67
  def rotate_ip
39
68
  return true if @ip_addresses.size == 1
40
69
 
@@ -54,6 +83,9 @@ module Server
54
83
  # in the server's IP list and the execution will be repeated for the next IP
55
84
  # address. If the IP rotation reaches the end of the list, the exception will
56
85
  # be reraised.
86
+ #
87
+ # @param [Proc] proc The action to be executed in a failsafe way
88
+ # @see #rotate_ip
57
89
  def failsafe(&proc)
58
90
  begin
59
91
  proc.call
@@ -63,4 +95,11 @@ module Server
63
95
  end
64
96
  end
65
97
 
98
+ # Initializes the socket(s) to communicate with the server
99
+ #
100
+ # @abstract Must be implemented in including classes to prepare sockets for
101
+ # server communication
102
+ def init_socket
103
+ end
104
+
66
105
  end
@@ -1,5 +1,5 @@
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
4
  # Copyright (c) 2008-2011, Sebastian Staudt
5
5
 
@@ -12,20 +12,45 @@ require 'steam/servers/game_server'
12
12
  require 'steam/sockets/rcon_socket'
13
13
  require 'steam/sockets/source_socket'
14
14
 
15
+ # This class represents a Source game server and can be used to query
16
+ # information about and remotely execute commands via RCON on the server
17
+ #
18
+ # A Source game server is an instance of the Source Dedicated Server (SrcDS)
19
+ # running games using Valve's Source engine, like Counter-Strike: Source,
20
+ # Team Fortress 2 or Left4Dead.
21
+ #
22
+ # @author Sebastian Staudt
23
+ # @see GoldSrcServer
15
24
  class SourceServer
16
25
 
17
26
  include GameServer
18
27
 
28
+ # Creates a new instance of a server object representing a Source server,
29
+ # i.e. SrcDS instance
30
+ #
31
+ # @param [String] address Either an IP address, a DNS name or one of them
32
+ # combined with the port number. If a port number is given, e.g.
33
+ # 'server.example.com:27016' it will override the second argument.
34
+ # @param [Fixnum] port The port the server is listening on
35
+ # @raise [SteamCondenserException] if an host name cannot be resolved
19
36
  def initialize(address, port = 27015)
20
37
  super
21
38
  end
22
39
 
23
40
  # Initializes the sockets to communicate with the Source server
41
+ #
42
+ # @see RCONSocket
43
+ # @see SourceSocket
24
44
  def init_socket
25
45
  @rcon_socket = RCONSocket.new @ip_address, @port
26
46
  @socket = SourceSocket.new @ip_address, @port
27
47
  end
28
48
 
49
+ # Authenticates the connection for RCON communication with the server
50
+ #
51
+ # @param [String] password The RCON password of the server
52
+ # @return [Boolean] whether authentication was successful
53
+ # @see #rcon_exec
29
54
  def rcon_auth(password)
30
55
  @rcon_request_id = rand 2**16
31
56
 
@@ -38,22 +63,21 @@ class SourceServer
38
63
  reply.request_id == @rcon_request_id
39
64
  end
40
65
 
66
+ # Remotely executes a command on the server via RCON
67
+ #
68
+ # @param [String] command The command to execute on the server via RCON
69
+ # @return [String] The output of the executed command
70
+ # @see #rcon_auth
41
71
  def rcon_exec(command)
42
72
  @rcon_socket.send RCONExecRequest.new(@rcon_request_id, command)
43
73
  @rcon_socket.send RCONTerminator.new(@rcon_request_id)
44
- response_packets = []
45
74
 
75
+ response = ''
46
76
  begin
47
77
  response_packet = @rcon_socket.reply
48
- redo if response_packet.nil?
49
78
  raise RCONNoAuthException.new if response_packet.is_a? RCONAuthResponse
50
- response_packets << response_packet
51
- end while response_packet.response.size > 0
52
-
53
- response = ''
54
- response_packets.each do |packet|
55
- response << packet.response
56
- end
79
+ response << response_packet.response
80
+ end while response.length == 0 || response_packet.response.size > 0
57
81
 
58
82
  response.strip
59
83
  end
@@ -1,44 +1,89 @@
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) 2010, Sebastian Staudt
4
+ # Copyright (c) 2010-2011, Sebastian Staudt
5
5
 
6
6
  require 'stringio'
7
7
 
8
+ # This extends the `StringIO` class from Ruby's standard library. It adds some
9
+ # methods to handle byte-wise input from a `StringIO` object.
10
+ #
11
+ # @author Sebastian Staudt
8
12
  class StringIO
9
13
 
14
+ # Creates a new instance of `StringIO` with the given size and fills it with
15
+ # zero-bytes.
16
+ #
17
+ # @param [Fixnum] size The size the new instance should have
18
+ # @return [StringIO] A new `StringIO` instance with the given size, filled
19
+ # with zero-bytes
10
20
  def self.allocate(size)
11
21
  new "\0" * size
12
22
  end
13
23
 
24
+ # Reads a single byte from the current position of the byte stream
25
+ #
26
+ # @return [Fixnum] The numeric value of the byte at the current position
14
27
  def byte
15
28
  read(1)[0].ord
16
29
  end
17
30
 
31
+ # Reads a floating-point integer (32 bit) from the current position of the
32
+ # byte stream
33
+ #
34
+ # @return [Float] The floating-point integer read from the byte stream
18
35
  def float
19
36
  read(4).unpack('e')[0]
20
37
  end
21
38
 
39
+ # Reads the whole remaining content of the byte stream from the current
40
+ # position to the end
41
+ #
42
+ # @return [String] The remainder of the byte stream starting from the current
43
+ # position of the byte stream
22
44
  def get
23
45
  read remaining
24
46
  end
25
47
 
48
+ # Reads an unsigned long integer (32 bit) from the current position of the
49
+ # byte stream
50
+ #
51
+ # @return [Fixnum] The unsigned long integer read from the byte stream
26
52
  def long
27
53
  read(4).unpack('V')[0]
28
54
  end
29
55
 
56
+ # Returns the remaining number of bytes from the current position to the end
57
+ # of the byte stream
58
+ #
59
+ # @return [Fixnum] The number of bytes until the end of the stream
30
60
  def remaining
31
61
  size - pos
32
62
  end
33
63
 
64
+ # Reads an unsigned short integer (16 bit) from the current position of the
65
+ # byte stream
66
+ #
67
+ # @return [Fixnum] The unsigned short integer read from the byte stream
34
68
  def short
35
69
  read(2).unpack('v')[0]
36
70
  end
37
71
 
72
+ # Reads a signed long integer (32 bit) from the current position of the byte
73
+ # stream
74
+ #
75
+ # @return [Fixnum] The signed long integer read from the byte stream
38
76
  def signed_long
39
77
  read(4).unpack('l')[0]
40
78
  end
41
79
 
80
+ # Reads a zero-byte terminated string from the current position of the byte
81
+ # stream
82
+ #
83
+ # This reads the stream up until the first occurance of a zero-byte or the
84
+ # end of the stream. The zero-byte is not included in the returned string.
85
+ #
86
+ # @return [String] The zero-byte terminated string read from the byte stream
42
87
  def cstring
43
88
  gets("\0")[0..-2]
44
89
  end