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,29 +1,35 @@
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 'stringio_additions'
6
+ require 'core_ext/stringio'
7
+ require 'errors/timeout_error'
7
8
  require 'steam/sockets/steam_socket'
8
9
 
9
- # The SourceSocket class is a sub class of SteamSocket respecting the
10
- # specifications of the Source query protocol.
10
+ # This class represents a socket used to communicate with game servers based on
11
+ # the Source engine (e.g. Team Fortress 2, Counter-Strike: Source)
12
+ #
13
+ # @author Sebastian Staudt
11
14
  class SourceSocket
12
15
 
13
16
  include SteamSocket
14
17
 
15
- # Reads a packet from the channel. The Source query protocol specifies a
16
- # maximum packet size of 1400 byte. Greater packets will be split over several
17
- # UDP packets. This method reassembles split packets into single packet
18
- # objects.
18
+ # Reads a packet from the socket
19
+ #
20
+ # The Source query protocol specifies a maximum packet size of 1,400 bytes.
21
+ # Bigger packets will be split over several UDP packets. This method
22
+ # reassembles split packets into single packet objects. Additionally Source
23
+ # may compress big packets using bzip2. Those packets will be compressed.
24
+ #
25
+ # @return [SteamPacket] The packet replied from the server
19
26
  def reply
20
- bytes_read = receive_packet 1400
27
+ receive_packet 1400
21
28
  is_compressed = false
22
29
 
23
30
  if @buffer.long == 0xFFFFFFFE
24
31
  split_packets = []
25
32
  begin
26
- # Parsing of split packet headers
27
33
  request_id = @buffer.long
28
34
  is_compressed = ((request_id & 0x80000000) != 0)
29
35
  packet_count = @buffer.byte
@@ -36,16 +42,14 @@ class SourceSocket
36
42
  @buffer.short
37
43
  end
38
44
 
39
- # Caching of split packet data
40
45
  split_packets[packet_number - 1] = @buffer.get
41
46
 
42
47
  puts "Received packet #{packet_number} of #{packet_count} for request ##{request_id}" if $DEBUG
43
48
 
44
- # Receiving the next packet
45
49
  if split_packets.size < packet_count
46
50
  begin
47
51
  bytes_read = receive_packet
48
- rescue TimeoutException
52
+ rescue SteamCondenser::TimeoutError
49
53
  bytes_read = 0
50
54
  end
51
55
  else
@@ -1,44 +1,41 @@
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
 
6
6
  require 'ipaddr'
7
7
  require 'socket'
8
8
 
9
- require 'stringio_additions'
10
- require 'exceptions/timeout_exception'
9
+ require 'core_ext/stringio'
10
+ require 'errors/timeout_error'
11
11
 
12
- # This module defines common methods for sockets used to connect to game and
13
- # master servers.
12
+ # This module implements common functionality for sockets used to connect to
13
+ # game and master servers
14
14
  #
15
15
  # @author Sebastian Staudt
16
- # @see GoldSrcSocket
17
- # @see MasterServerSocket
18
- # @see RCONSocket
19
- # @see SourceSocket
20
- # @since 0.5.0
21
16
  module SteamSocket
22
17
 
23
- # The default timeout
24
- # @since 0.11.0
18
+ # The default socket timeout
25
19
  @@timeout = 1000
26
20
 
27
- # Sets the timeout for socket operations. This usually only affects timeouts,
28
- # i.e. when a server does not respond in time.
21
+ # Sets the timeout for socket operations
29
22
  #
30
- # Due to the server-side implementation of the RCON protocol, each RCON
31
- # request will also wait this amount of time after execution. So if you need
32
- # RCON requests to execute fast, you should set this to a adequatly low
33
- # value.
23
+ # Any request that takes longer than this time will cause a {TimeoutError}.
34
24
  #
35
- # +timeout+ The amount of milliseconds before a request times out
25
+ # @param [Fixnum] timeout The amount of milliseconds before a request times
26
+ # out
36
27
  def self.timeout=(timeout)
37
28
  @@timeout = timeout
38
29
  end
39
30
 
31
+ # Creates a new UDP socket to communicate with the server on the given IP
32
+ # address and port
33
+ #
34
+ # @param [String] ip_address Either the IP address or the DNS name of the
35
+ # server
36
+ # @param [Fixnum] port The port the server is listening on
40
37
  def initialize(ip_address, port = 27015)
41
- @socket = UDPSocket.new
38
+ @socket = UDPSocket.new
42
39
  @socket.connect ip_address, port
43
40
  end
44
41
 
@@ -47,15 +44,21 @@ module SteamSocket
47
44
  @socket.close
48
45
  end
49
46
 
47
+ # Reads the given amount of data from the socket and wraps it into the buffer
48
+ #
49
+ # @param [Fixnum] buffer_length The data length to read from the socket
50
+ # @raise [SteamCondenser::TimeoutError] if no packet is received on time
51
+ # @return [Fixnum] The number of bytes that have been read from the socket
52
+ # @see StringIO
50
53
  def receive_packet(buffer_length = 0)
51
54
  if select([@socket], nil, nil, @@timeout / 1000.0).nil?
52
- raise TimeoutException
55
+ raise SteamCondenser::TimeoutError
53
56
  end
54
57
 
55
58
  if buffer_length == 0
56
59
  @buffer.rewind
57
60
  else
58
- @buffer = StringIO.allocate buffer_length
61
+ @buffer = StringIO.alloc buffer_length
59
62
  end
60
63
 
61
64
  data = @socket.recv @buffer.remaining
@@ -66,6 +69,13 @@ module SteamSocket
66
69
  bytes_read
67
70
  end
68
71
 
72
+ # Sends the given packet to the server
73
+ #
74
+ # This converts the packet into a byte stream first before writing it to the
75
+ # socket.
76
+ #
77
+ # @param [SteamPacket] data_packet The packet to send to the server
78
+ # @see SteamPacket#to_s
69
79
  def send(data_packet)
70
80
  puts "Sending data packet of type \"#{data_packet.class.to_s}\"." if $DEBUG
71
81
 
@@ -1,17 +1,81 @@
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
 
6
- require 'exceptions/steam_condenser_exception'
6
+ require 'errors/steam_condenser_error'
7
7
 
8
8
  # The SteamPlayer class represents a player connected to a server
9
+ #
10
+ # @author Sebastian Staudt
9
11
  class SteamPlayer
10
12
 
11
- attr_reader :client_port, :connect_time, :id, :ip_address, :name, :loss,
12
- :ping, :rate, :real_id, :score, :state, :steam_id
13
+ # Returns the client port of this player
14
+ #
15
+ # @return [Fixnum] The client port of the player
16
+ attr_reader :client_port
17
+
18
+ # Returns the time this player is connected to the server
19
+ #
20
+ # @return [Float] The connection time of the player
21
+ attr_reader :connect_time
22
+
23
+ # Returns the ID of this player
24
+ #
25
+ # @return [Fixnum] The ID of this player
26
+ attr_reader :id
27
+
28
+ # Returns the IP address of this player
29
+ #
30
+ # @return [String] The IP address of this player
31
+ attr_reader :ip_address
32
+
33
+ # Returns the nickname of this player
34
+ #
35
+ # @return [String] The name of this player
36
+ attr_reader :name
37
+
38
+ # Returns the packet loss of this player's connection
39
+ #
40
+ # @return [String] The packet loss of this player's connection
41
+ attr_reader :loss
42
+
43
+ # Returns the ping of this player
44
+ #
45
+ # @return [Fixnum] The ping of this player
46
+ attr_reader :ping
47
+
48
+ # Returns the rate of this player
49
+ #
50
+ # @return [Fixnum] The rate of this player
51
+ attr_reader :rate
52
+
53
+ # Returns the real ID (as used on the server) of this player
54
+ #
55
+ # @return [Fixnum] The real ID of this player
56
+ attr_reader :real_id
57
+
58
+ # Returns the score of this player
59
+ #
60
+ # @return [Fixnum] The score of this player
61
+ attr_reader :score
62
+
63
+ # Returns the connection state of this player
64
+ #
65
+ # @return [String] The connection state of this player
66
+ attr_reader :state
67
+
68
+ # Returns the SteamID of this player
69
+ #
70
+ # @return [String] The SteamID of this player
71
+ attr_reader :steam_id
13
72
 
14
- # Creates a new SteamPlayer object based on the given information
73
+ # Creates a new player instance with the given information
74
+ #
75
+ # @param [Fixnum] id The ID of the player on the server
76
+ # @param [String] name The name of the player
77
+ # @param [Fixnum] score The score of the player
78
+ # @param [Float] connect_time The time the player is connected to the server
15
79
  def initialize(id, name, score, connect_time)
16
80
  @connect_time = connect_time
17
81
  @id = id
@@ -20,11 +84,15 @@ class SteamPlayer
20
84
  @extended = false
21
85
  end
22
86
 
23
- # Extends this player object with additional information acquired using RCON
24
- # status
87
+ # Extends a player object with information retrieved from a RCON call to
88
+ # the status command
89
+ #
90
+ # @param [String] player_data The player data retrieved from `rcon status`
91
+ # @raise [SteamCondenserError] if the information belongs to another
92
+ # player
25
93
  def add_info(player_data)
26
94
  unless player_data[:name] == @name
27
- raise SteamCondenserException.new('Information to add belongs to a different player.')
95
+ raise SteamCondenserError, 'Information to add belongs to a different player.'
28
96
  end
29
97
 
30
98
  @extended = true
@@ -47,16 +115,23 @@ class SteamPlayer
47
115
  end
48
116
 
49
117
  # Returns whether this player is a bot
118
+ #
119
+ # @return bool `true` if this player is a bot
50
120
  def bot?
51
121
  @steam_id == 'BOT'
52
122
  end
53
123
 
54
- # Returns whether this player object has extended information
124
+ # Returns whether this player object has extended information gathered
125
+ # using RCON
126
+ #
127
+ # @return bool `true` if extended information for this player is available
55
128
  def extended?
56
129
  @extended
57
130
  end
58
131
 
59
- # Returns a String representation of this player
132
+ # Returns a string representation of this player
133
+ #
134
+ # @return [String] A string representing this player
60
135
  def to_s
61
136
  if @extended
62
137
  "\##{@real_id} \"#{@name}\", SteamID: #{@steam_id}, Score: #{@score}, Time: #{@connect_time}"
@@ -1,24 +1,24 @@
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) 2010-2011, Sebastian Staudt
5
-
6
- module SteamCondenser
7
-
8
- require 'steam-condenser/version'
9
-
10
- # This module is used as a wrapper around Steam Condenser's Steam Community
11
- # classes
12
- #
13
- # It does not provide any own functionality, but this file is used to easily
14
- # require classes to interact with the Steam Community
15
- #
16
- # @author Sebastian Staudt
17
- module Community
18
-
19
- require 'steam/community/steam_id'
20
- require 'steam/community/web_api'
21
-
22
- end
23
-
24
- end
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) 2010-2011, Sebastian Staudt
5
+
6
+ module SteamCondenser
7
+
8
+ require 'steam-condenser/version'
9
+
10
+ # This module is used as a wrapper around Steam Condenser's Steam Community
11
+ # classes
12
+ #
13
+ # It does not provide any own functionality, but this file is used to easily
14
+ # require classes to interact with the Steam Community
15
+ #
16
+ # @author Sebastian Staudt
17
+ module Community
18
+
19
+ require 'steam/community/steam_id'
20
+ require 'steam/community/web_api'
21
+
22
+ end
23
+
24
+ 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) 2010-2011, Sebastian Staudt
5
5
 
@@ -1,11 +1,11 @@
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) 2010-2011, Sebastian Staudt
5
5
 
6
6
  module SteamCondenser
7
7
 
8
8
  # The current version of Steam Condenser
9
- VERSION = '0.14.0'
9
+ VERSION = '1.0.1'
10
10
 
11
11
  end
@@ -0,0 +1,23 @@
1
+ require 'bundler'
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/lib/steam-condenser/version')
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'steam-condenser'
7
+ s.version = SteamCondenser::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = [ 'Sebastian Staudt' ]
10
+ s.email = [ 'koraktor@gmail.com' ]
11
+ s.homepage = 'http://koraktor.de/steam-condenser'
12
+ s.summary = 'Steam Condenser - A Steam query library'
13
+ s.description = 'A multi-language library for querying the Steam Community, Source, GoldSrc servers and Steam master servers'
14
+
15
+ s.add_dependency 'bzip2-ruby', '~> 0.2.7'
16
+ s.add_dependency 'multi_json', '~> 1.0.3'
17
+ s.add_development_dependency 'rake', '~> 0.9.2'
18
+ s.add_development_dependency 'yard', '~> 0.7.2'
19
+
20
+ s.files = FileList['**/*']
21
+ s.test_files = FileList['test/**/*']
22
+ s.require_paths = [ 'lib' ]
23
+ end
data/test/query_tests.rb CHANGED
@@ -1,14 +1,14 @@
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 'ipaddr'
7
7
  require 'test/unit'
8
8
 
9
9
  $:.push File.join(File.dirname(__FILE__), '..', 'lib')
10
10
 
11
- require 'exceptions/timeout_exception'
11
+ require 'errors/timeout_error'
12
12
  require 'steam/servers/goldsrc_server'
13
13
  require 'steam/servers/master_server'
14
14
  require 'steam/servers/source_server'
@@ -17,16 +17,16 @@ class QueryTests < Test::Unit::TestCase
17
17
 
18
18
  # This test tries to initialize an invalid GoldSrc server
19
19
  def test_invalid_goldsrc_server
20
- assert_raise TimeoutException do
21
- invalid_server = GoldSrcServer.new IPAddr.new("1.0.0.0")
20
+ assert_raise SteamCondenser::TimeoutError do
21
+ invalid_server = GoldSrcServer.new IPAddr.new('1.0.0.0')
22
22
  invalid_server.ping
23
23
  end
24
24
  end
25
25
 
26
26
  # This test tries to initialize an invalid Source server
27
27
  def test_invalid_source_server
28
- assert_raise TimeoutException do
29
- invalid_server = SourceServer.new IPAddr.new("1.0.0.0")
28
+ assert_raise SteamCondenser::TimeoutError do
29
+ invalid_server = SourceServer.new IPAddr.new('1.0.0.0')
30
30
  invalid_server.ping
31
31
  end
32
32
  end
@@ -36,9 +36,9 @@ class QueryTests < Test::Unit::TestCase
36
36
  def test_random_goldsrc_server
37
37
  assert_nothing_raised do
38
38
  master_server = MasterServer.new(*MasterServer::GOLDSRC_MASTER_SERVER)
39
- servers = master_server.servers MasterServer::REGION_ALL, "\\type\\d\\empty\\1\\full\\1\\gamedir\\valve"
39
+ servers = master_server.servers MasterServer::REGION_ALL, '\type\d\empty\1\full\1\gamedir\valve'
40
40
 
41
- assert !servers.empty?, "Got no servers from master server."
41
+ assert !servers.empty?, 'Got no servers from master server.'
42
42
 
43
43
  server = GoldSrcServer.new(*servers[rand(servers.length)])
44
44
  server.init
@@ -54,9 +54,9 @@ class QueryTests < Test::Unit::TestCase
54
54
  def test_random_source_server
55
55
  assert_nothing_raised do
56
56
  master_server = MasterServer.new(*MasterServer::SOURCE_MASTER_SERVER)
57
- servers = master_server.servers MasterServer::REGION_ALL, "\\type\\d\\empty\\1\\full\\1\\gamedir\\tf"
57
+ servers = master_server.servers MasterServer::REGION_ALL, '\type\d\empty\1\full\1\gamedir\tf'
58
58
 
59
- assert !servers.empty?, "Got no servers from master server."
59
+ assert !servers.empty?, 'Got no servers from master server.'
60
60
 
61
61
  server = SourceServer.new(*servers[rand(servers.length)])
62
62
  server.init