steam-condenser 0.13.1 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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