minecraft-query 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,25 @@
1
- module MinecraftQuery
2
- require 'socket'
3
- require 'timeout'
4
-
5
- require 'query/query'
6
- require 'rcon/rcon'
7
-
8
- #
9
- # Connects to a Minecraft server's RCON or Query port to send commands or fetch data.
10
- #
11
- # Example:
12
- # >> rcon = RCON::Minecraft.new('localhost', 25575)
13
- # => #<RCON::Minecraft:0x007ff6e29e1228 @host="localhost", @port=25575, @socket=nil, @packet=nil, @authed=false, @return_packets=false>
14
- # >> rcon.auth('password')
15
- # => true
16
- # >> rcon.command('say hi')
17
- # => "\xA7d[Server] hi\n"
18
- #
19
- # >> query = Query::simpleQuery('localhost', 25565)
20
- # => {:motd=>"ECS Survival", :gametype=>"SMP", :map=>"world", :numplayers=>"1", :maxplayers=>"20"}
21
- # >> players = query[:numplayers] + '/' + query[:maxplayers]
22
- # => 1/20
23
- #
24
-
1
+ module MinecraftQuery
2
+ require 'socket'
3
+ require 'timeout'
4
+
5
+ require 'query/query'
6
+ # require 'rcon/rcon'
7
+
8
+ #
9
+ # Connects to a Minecraft server's RCON or Query port to send commands or fetch data.
10
+ #
11
+ # Example:
12
+ # >> rcon = RCON::Minecraft.new('localhost', 25575)
13
+ # => #<RCON::Minecraft:0x007ff6e29e1228 @host="localhost", @port=25575, @socket=nil, @packet=nil, @authed=false, @return_packets=false>
14
+ # >> rcon.auth('password')
15
+ # => true
16
+ # >> rcon.command('say hi')
17
+ # => "\xA7d[Server] hi\n"
18
+ #
19
+ # >> query = Query::simpleQuery('localhost', 25565)
20
+ # => {:motd=>"ECS Survival", :gametype=>"SMP", :map=>"world", :numplayers=>"1", :maxplayers=>"20"}
21
+ # >> players = query[:numplayers] + '/' + query[:maxplayers]
22
+ # => 1/20
23
+ #
24
+
25
25
  end
@@ -1,90 +1,93 @@
1
1
  class Query
2
- def self.init
3
- @sock = UDPSocket.new
4
- @sock.connect(@addr,@port)
5
- @val = {}
6
- @buff = nil
7
- key
8
- end
9
-
10
- def self.key
11
- begin
12
- timeout(1) do
13
- start = @sock.send("\xFE\xFD\x09\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT), 0)
14
- t = @sock.recvfrom(1460)[0]
15
- key = t[5...-1].to_i
16
- @key = Array(key).pack('N')
17
- end
18
- rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
19
- return false
2
+ def self.init
3
+ @sock = UDPSocket.new
4
+ @sock.connect(@addr,@port)
5
+ @val = {}
6
+ @buff = nil
7
+ key
20
8
  end
21
- end
22
-
23
-
24
- def self.simpleQuery(addr = 'localhost', port = 25565)
25
- @addr = addr
26
- @port = port
27
- init
28
- begin
29
- timeout(1) do
30
- query = @sock.send("\xFE\xFD\x00\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT) + @key, 0)
31
- data = @sock.recvfrom(1460)[0]
32
- buffer = data[5...-1]
33
- @val[:motd], @val[:gametype], @val[:map], @val[:numplayers], @val[:maxplayers], @buf = buffer.split("\x00", 6)
34
- if @sock != nil
35
- @sock.close
9
+
10
+ def self.key
11
+ begin
12
+ timeout(1) do
13
+ start = @sock.send("\xFE\xFD\x09\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT), 0)
14
+ t = @sock.recvfrom(1460)[0]
15
+ key = t[5...-1].to_i
16
+ @key = Array(key).pack('N')
17
+ end
18
+ rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
19
+ return "Host unreachable, check your configuration and try again"
20
+ return "An Exception occured, please check last message"
21
+ rescue StandardError => e
22
+ return "An other error occured. Check your ruby installation and tell the tech monkey this:"
23
+ return e
36
24
  end
37
- end
38
- return @val
39
- rescue StandardError => e
40
- return false
41
25
  end
42
- end
43
-
44
- def self.fullQuery(addr = 'localhost', port = 25565)
45
- @addr = addr
46
- @port = port
47
- init
48
- begin
49
- timeout(1) do
50
- query = @sock.send("\xFE\xFD\x00\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT) + @key + "\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT), 0)
51
- data = @sock.recvfrom(1460)[0]
52
- buffer = data[11...-1]
53
- items, players = buffer.split("\x00\x00\x01player_\x00\x00".force_encoding(Encoding::ASCII_8BIT))
54
- if items[0...8] == 'hostname'
55
- items = 'motd' + items[8...-1]
26
+
27
+ def self.simpleQuery(addr = 'localhost', port = 25565)
28
+ @addr = addr
29
+ @port = port
30
+ init
31
+ begin
32
+ timeout(1) do
33
+ query = @sock.send("\xFE\xFD\x00\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT) + @key.to_s, 0)
34
+ data = @sock.recvfrom(1460)[0]
35
+ buffer = data[5...-1]
36
+ @val[:motd], @val[:gametype], @val[:map], @val[:numplayers], @val[:maxplayers], @buf = buffer.split("\x00", 6)
37
+ if @sock != nil
38
+ @sock.close
39
+ end
40
+ end
41
+ return @val
42
+ rescue StandardError => e
43
+ return e
56
44
  end
57
- vals = {}
58
- items = items.split("\x00")
59
- items.each_with_index do |key, idx|
60
- next unless idx % 2 == 0
61
- vals[key] = items[idx + 1]
62
- end
63
-
64
- vals["motd"] = vals["hostname"]
65
- vals.delete("hostname")
66
- vals.delete("um") if vals["um"]
67
-
68
- players = players[0..-2] if players
69
-
70
- if players
71
- vals[:players] = players.split("\x00")
72
- end
73
- puts vals
74
- vals["raw_plugins"] = vals["plugins"]
75
- parts = vals["raw_plugins"].split(":")
76
- server = parts[0].strip() if parts[0]
77
- plugins = []
78
- if parts.size == 2
79
- plugins = parts[1].split(";").map {|value| value.strip() }
45
+ end
46
+
47
+ def self.fullQuery(addr = 'localhost', port = 25565)
48
+ @addr = addr
49
+ @port = port
50
+ init
51
+ begin
52
+ timeout(1) do
53
+ query = @sock.send("\xFE\xFD\x00\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT) + @key.to_s + "\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT), 0)
54
+ data = @sock.recvfrom(1460)[0]
55
+ buffer = data[11...-1]
56
+ items, players = buffer.split("\x00\x00\x01player_\x00\x00".force_encoding(Encoding::ASCII_8BIT))
57
+ if items[0...8] == 'hostname'
58
+ items = 'motd' + items[8...-1]
59
+ end
60
+ vals = {}
61
+ items = items.split("\x00")
62
+ items.each_with_index do |key, idx|
63
+ next unless idx % 2 == 0
64
+ vals[key] = items[idx + 1]
65
+ end
66
+
67
+ vals["motd"] = vals["hostname"]
68
+ vals.delete("hostname")
69
+ vals.delete("um") if vals["um"]
70
+
71
+ players = players[0..-2] if players
72
+ if players
73
+ vals[:players] = players.split("\x00")
74
+ end
75
+ puts vals
76
+ vals["raw_plugins"] = vals["plugins"]
77
+ parts = vals["raw_plugins"].split(":")
78
+ server = parts[0].strip() if parts[0]
79
+ plugins = []
80
+ if parts.size == 2
81
+ plugins = parts[1].split(";").map {|value| value.strip() }
82
+ end
83
+ vals["plugins"] = plugins
84
+ vals["server"] = server
85
+ vals["timestamp"] = Time.now
86
+ return vals.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
87
+ end
88
+ rescue StandardError => e
89
+ return "An other error occured. Check your minecraft config and tell the tech monkey this:"
90
+ raise e
80
91
  end
81
- vals["plugins"] = plugins
82
- vals["server"] = server
83
- vals["timestamp"] = Time.now
84
- return vals.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
85
- end
86
- rescue StandardError => e
87
- return false
88
92
  end
89
- end
90
- end
93
+ end
@@ -1,435 +1,436 @@
1
- module RCON
2
-
3
- class Query
4
-
5
- #
6
- # Convenience method to scrape input from cvar output and return that data.
7
- # Returns integers as a numeric type if possible.
8
- #
9
- # ex: rcon.cvar("mp_friendlyfire") => 1
10
- #
11
-
12
- def cvar(cvar_name)
13
- response = command(cvar_name)
14
- match = /^.+?\s(?:is|=)\s"([^"]+)".*$/.match response
15
- match = match[1]
16
- if /\D/.match match
17
- return match
18
- else
19
- return match.to_i
20
- end
21
- end
22
- end
23
-
24
- class Packet
25
- end
26
-
27
- class Packet::Source
28
- # execution command
29
- COMMAND_EXEC = 2
30
- # auth command
31
- COMMAND_AUTH = 3
32
- # auth response
33
- RESPONSE_AUTH = 2
34
- # normal response
35
- RESPONSE_NORM = 0
36
- # packet trailer
37
- TRAILER = "\x00\x00"
38
-
39
- # size of the packet (10 bytes for header + string1 length)
40
- attr_accessor :packet_size
41
- # Request Identifier, used in managing multiple requests at once
42
- attr_accessor :request_id
43
- # Type of command, normally COMMAND_AUTH or COMMAND_EXEC. In response packets, RESPONSE_AUTH or RESPONSE_NORM
44
- attr_accessor :command_type
45
- # First string, the only used one in the protocol, contains
46
- # commands and responses. Null terminated.
47
- attr_accessor :string1
48
- # Second string, unused by the protocol. Null terminated.
49
- attr_accessor :string2
50
-
51
- #
52
- # Generate a command packet to be sent to an already
53
- # authenticated RCon connection. Takes the command as an
54
- # argument.
55
- #
56
- def command(string)
57
- @request_id = rand(1000)
58
- @string1 = string
59
- @string2 = TRAILER
60
- @command_type = COMMAND_EXEC
61
-
62
- @packet_size = build_packet.length
63
-
64
- return self
65
- end
66
-
67
- #
68
- # Generate an authentication packet to be sent to a newly
69
- # started RCon connection. Takes the RCon password as an
70
- # argument.
71
- #
72
- def auth(string)
73
- @request_id = rand(1000)
74
- @string1 = string
75
- @string2 = TRAILER
76
- @command_type = COMMAND_AUTH
77
-
78
- @packet_size = build_packet.length
79
-
80
- return self
81
- end
82
-
83
- #
84
- # Builds a packet ready to deliver, without the size prepended.
85
- # Used to calculate the packet size, use #to_s to get the packet
86
- # that srcds actually needs.
87
- #
88
- def build_packet
89
- return [@request_id, @command_type, @string1, @string2].pack("VVa#{@string1.length}a2")
90
- end
91
-
92
- # Returns a string representation of the packet, useful for
93
- # sending and debugging. This include the packet size.
94
- def to_s
95
- packet = build_packet
96
- @packet_size = packet.length
97
- return [@packet_size].pack("V") + packet
98
- end
99
-
100
- end
101
-
102
- class Source < Query
103
- # Packet::Source object that was sent as a result of the last query
104
- attr_reader :packet
105
- # TCPSocket object
106
- attr_reader :socket
107
- # Host of connection
108
- attr_reader :host
109
- # Port of connection
110
- attr_reader :port
111
- # Authentication Status
112
- attr_reader :authed
113
- # return full packet, or just data?
114
- attr_accessor :return_packets
115
-
116
- #
117
- # Given a host and a port (dotted-quad or hostname OK), creates
118
- # a Query::Source object. Note that this will still
119
- # require an authentication packet (see the auth() method)
120
- # before commands can be sent.
121
- #
122
-
123
- def initialize(host = 'localhost', port = 25575)
124
- @host = host
125
- @port = port
126
- @socket = nil
127
- @packet = nil
128
- @authed = false
129
- @return_packets = false
130
- end
131
-
132
- #
133
- # See Query#cvar.
134
- #
135
-
136
- def cvar(cvar_name)
137
- return_packets = @return_packets
138
- @return_packets = false
139
- response = super
140
- @return_packets = return_packets
141
- return response
142
- end
143
-
144
- #
145
- # Sends a RCon command to the server. May be used multiple times
146
- # after an authentication is successful.
147
- #
148
-
149
- def command(command)
150
-
151
- if ! @authed
152
- raise NetworkException.new("You must authenticate the connection successfully before sending commands.")
153
- end
154
-
155
- @packet = Packet::Source.new
156
- @packet.command(command)
157
-
158
- @socket.print @packet.to_s
159
- rpacket = build_response_packet
160
-
161
- if rpacket.command_type != Packet::Source::RESPONSE_NORM
162
- raise NetworkException.new("error sending command: #{rpacket.command_type}")
163
- end
164
-
165
- if @return_packets
166
- return rpacket
167
- else
168
- return rpacket.string1
169
- end
170
- end
171
-
172
- #
173
- # Requests authentication from the RCon server, given a
174
- # password. Is only expected to be used once.
175
- #
176
-
177
- def auth(password)
178
- establish_connection
179
-
180
- @packet = Packet::Source.new
181
- @packet.auth(password)
182
-
183
- @socket.print @packet.to_s
184
- # on auth, one junk packet is sent
185
- rpacket = nil
186
- 2.times { rpacket = build_response_packet }
187
-
188
- if rpacket.command_type != Packet::Source::RESPONSE_AUTH
189
- raise NetworkException.new("error authenticating: #{rpacket.command_type}")
190
- end
191
-
192
- @authed = true
193
- if @return_packets
194
- return rpacket
195
- else
196
- return true
197
- end
198
- end
199
-
200
- alias_method :authenticate, :auth
201
-
202
- #
203
- # Disconnects from the Source server.
204
- #
205
-
206
- def disconnect
207
- if @socket
208
- @socket.close
209
- @socket = nil
210
- @authed = false
211
- end
212
- end
213
-
214
- protected
215
-
216
- #
217
- # Builds a Packet::Source packet based on the response
218
- # given by the server.
219
- #
220
- def build_response_packet
221
- rpacket = Packet::Source.new
222
- total_size = 0
223
- request_id = 0
224
- type = 0
225
- response = ""
226
- message = ""
227
-
228
-
229
- loop do
230
- break unless IO.select([@socket], nil, nil, 10)
231
-
232
- #
233
- # TODO: clean this up - read everything and then unpack.
234
- #
235
-
236
- tmp = @socket.recv(14)
237
- if tmp.nil?
238
- return nil
239
- end
240
- size, request_id, type, message = tmp.unpack("VVVa*")
241
- total_size += size
242
-
243
- # special case for authentication
244
- break if message.sub! /\x00\x00$/, ""
245
-
246
- response << message
247
-
248
- # the 'size - 10' here accounts for the fact that we've snarfed 14 bytes,
249
- # the size (which is 4 bytes) is not counted, yet represents the rest
250
- # of the packet (which we have already taken 10 bytes from)
251
-
252
- tmp = @socket.recv(size - 10)
253
- response << tmp
254
- response.sub! /\x00\x00$/, ""
255
- end
256
-
257
- rpacket.packet_size = total_size
258
- rpacket.request_id = request_id
259
- rpacket.command_type = type
260
-
261
- # strip nulls (this is actually the end of string1 and string2)
262
- rpacket.string1 = response.sub /\x00\x00$/, ""
263
- return rpacket
264
- end
265
-
266
- # establishes a connection to the server.
267
- def establish_connection
268
- if @socket.nil?
269
- @socket = TCPSocket.new(@host, @port)
270
- end
271
- end
272
-
273
- end
274
-
275
- class Minecraft < Query
276
- # Packet::Source object that was sent as a result of the last query
277
- attr_reader :packet
278
- # TCPSocket object
279
- attr_reader :socket
280
- # Host of connection
281
- attr_reader :host
282
- # Port of connection
283
- attr_reader :port
284
- # Authentication Status
285
- attr_reader :authed
286
- # return full packet, or just data?
287
- attr_accessor :return_packets
288
-
289
- #
290
- # Given a host and a port (dotted-quad or hostname OK), creates
291
- # a Query::Minecraft object. Note that this will still
292
- # require an authentication packet (see the auth() method)
293
- # before commands can be sent.
294
- #
295
-
296
- def initialize(host = 'localhost', port = 25575)
297
- @host = host
298
- @port = port
299
- @socket = nil
300
- @packet = nil
301
- @authed = false
302
- @return_packets = false
303
- end
304
-
305
- #
306
- # See Query#cvar.
307
- #
308
-
309
- def cvar(cvar_name)
310
- return_packets = @return_packets
311
- @return_packets = false
312
- response = super
313
- @return_packets = return_packets
314
- return response
315
- end
316
-
317
- #
318
- # Sends a RCon command to the server. May be used multiple times
319
- # after an authentication is successful.
320
- #
321
-
322
- def command(command)
323
-
324
- if ! @authed
325
- raise NetworkException.new("You must authenticate the connection successfully before sending commands.")
326
- end
327
-
328
- @packet = Packet::Source.new
329
- @packet.command(command)
330
-
331
- @socket.print @packet.to_s
332
- rpacket = build_response_packet
333
-
334
- if rpacket.command_type != Packet::Source::RESPONSE_NORM
335
- raise NetworkException.new("error sending command: #{rpacket.command_type}")
336
- end
337
-
338
- if @return_packets
339
- return rpacket
340
- else
341
- return rpacket.string1
342
- end
343
- end
344
-
345
- #
346
- # Requests authentication from the RCon server, given a
347
- # password. Is only expected to be used once.
348
- #
349
-
350
-
351
- def auth(password)
352
- establish_connection
353
-
354
- @packet = Packet::Source.new
355
- @packet.auth(password)
356
-
357
- @socket.print @packet.to_s
358
- rpacket = nil
359
- rpacket = build_response_packet
360
-
361
- if rpacket.command_type != Packet::Source::RESPONSE_AUTH
362
- raise NetworkException.new("error authenticating: #{rpacket.command_type}")
363
- end
364
-
365
- @authed = true
366
- if @return_packets
367
- return rpacket
368
- else
369
- return true
370
- end
371
- end
372
-
373
- alias_method :authenticate, :auth
374
-
375
- #
376
- # Disconnects from the Minecraft server.
377
- #
378
-
379
- def disconnect
380
- if @socket
381
- @socket.close
382
- @socket = nil
383
- @authed = false
384
- end
385
- end
386
-
387
- protected
388
-
389
- #
390
- # Builds a Packet::Source packet based on the response
391
- # given by the server.
392
- #
393
- def build_response_packet
394
- rpacket = Packet::Source.new
395
- total_size = 0
396
- request_id = 0
397
- type = 0
398
- response = ""
399
- message = ""
400
- message2 = ""
401
-
402
- tmp = @socket.recv(4)
403
- if tmp.nil?
404
- return nil
405
- end
406
- size = tmp.unpack("V1")
407
- tmp = @socket.recv(size[0])
408
- request_id, type, message, message2 = tmp.unpack("V1V1a*a*")
409
- total_size = size[0]
410
-
411
- rpacket.packet_size = total_size
412
- rpacket.request_id = request_id
413
- rpacket.command_type = type
414
-
415
- # strip nulls (this is actually the end of string1 and string2)
416
- message.sub! /\x00\x00$/, ""
417
- message2.sub! /\x00\x00$/, ""
418
- rpacket.string1 = message
419
- rpacket.string2 = message2
420
- return rpacket
421
- end
422
-
423
- # establishes a connection to the server.
424
- def establish_connection
425
- if @socket.nil?
426
- @socket = TCPSocket.new(@host, @port)
427
- end
428
- end
429
-
430
- end
431
-
432
- class NetworkException < Exception
433
- end
434
-
1
+ module RCON
2
+
3
+ class Query
4
+
5
+ #
6
+ # Convenience method to scrape input from cvar output and return that data.
7
+ # Returns integers as a numeric type if possible.
8
+ #
9
+ # ex: rcon.cvar("mp_friendlyfire") => 1
10
+ #
11
+ # NOTE: This file has not been updated since previous version. Please be aware there may be outstanding Ruby2 bugs
12
+
13
+ def cvar(cvar_name)
14
+ response = command(cvar_name)
15
+ match = /^.+?\s(?:is|=)\s"([^"]+)".*$/.match response
16
+ match = match[1]
17
+ if /\D/.match match
18
+ return match
19
+ else
20
+ return match.to_i
21
+ end
22
+ end
23
+ end
24
+
25
+ class Packet
26
+ end
27
+
28
+ class Packet::Source
29
+ # execution command
30
+ COMMAND_EXEC = 2
31
+ # auth command
32
+ COMMAND_AUTH = 3
33
+ # auth response
34
+ RESPONSE_AUTH = 2
35
+ # normal response
36
+ RESPONSE_NORM = 0
37
+ # packet trailer
38
+ TRAILER = "\x00\x00"
39
+
40
+ # size of the packet (10 bytes for header + string1 length)
41
+ attr_accessor :packet_size
42
+ # Request Identifier, used in managing multiple requests at once
43
+ attr_accessor :request_id
44
+ # Type of command, normally COMMAND_AUTH or COMMAND_EXEC. In response packets, RESPONSE_AUTH or RESPONSE_NORM
45
+ attr_accessor :command_type
46
+ # First string, the only used one in the protocol, contains
47
+ # commands and responses. Null terminated.
48
+ attr_accessor :string1
49
+ # Second string, unused by the protocol. Null terminated.
50
+ attr_accessor :string2
51
+
52
+ #
53
+ # Generate a command packet to be sent to an already
54
+ # authenticated RCon connection. Takes the command as an
55
+ # argument.
56
+ #
57
+ def command(string)
58
+ @request_id = rand(1000)
59
+ @string1 = string
60
+ @string2 = TRAILER
61
+ @command_type = COMMAND_EXEC
62
+
63
+ @packet_size = build_packet.length
64
+
65
+ return self
66
+ end
67
+
68
+ #
69
+ # Generate an authentication packet to be sent to a newly
70
+ # started RCon connection. Takes the RCon password as an
71
+ # argument.
72
+ #
73
+ def auth(string)
74
+ @request_id = rand(1000)
75
+ @string1 = string
76
+ @string2 = TRAILER
77
+ @command_type = COMMAND_AUTH
78
+
79
+ @packet_size = build_packet.length
80
+
81
+ return self
82
+ end
83
+
84
+ #
85
+ # Builds a packet ready to deliver, without the size prepended.
86
+ # Used to calculate the packet size, use #to_s to get the packet
87
+ # that srcds actually needs.
88
+ #
89
+ def build_packet
90
+ return [@request_id, @command_type, @string1, @string2].pack("VVa#{@string1.length}a2")
91
+ end
92
+
93
+ # Returns a string representation of the packet, useful for
94
+ # sending and debugging. This include the packet size.
95
+ def to_s
96
+ packet = build_packet
97
+ @packet_size = packet.length
98
+ return [@packet_size].pack("V") + packet
99
+ end
100
+
101
+ end
102
+
103
+ class Source < Query
104
+ # Packet::Source object that was sent as a result of the last query
105
+ attr_reader :packet
106
+ # TCPSocket object
107
+ attr_reader :socket
108
+ # Host of connection
109
+ attr_reader :host
110
+ # Port of connection
111
+ attr_reader :port
112
+ # Authentication Status
113
+ attr_reader :authed
114
+ # return full packet, or just data?
115
+ attr_accessor :return_packets
116
+
117
+ #
118
+ # Given a host and a port (dotted-quad or hostname OK), creates
119
+ # a Query::Source object. Note that this will still
120
+ # require an authentication packet (see the auth() method)
121
+ # before commands can be sent.
122
+ #
123
+
124
+ def initialize(host = 'localhost', port = 25575)
125
+ @host = host
126
+ @port = port
127
+ @socket = nil
128
+ @packet = nil
129
+ @authed = false
130
+ @return_packets = false
131
+ end
132
+
133
+ #
134
+ # See Query#cvar.
135
+ #
136
+
137
+ def cvar(cvar_name)
138
+ return_packets = @return_packets
139
+ @return_packets = false
140
+ response = super
141
+ @return_packets = return_packets
142
+ return response
143
+ end
144
+
145
+ #
146
+ # Sends a RCon command to the server. May be used multiple times
147
+ # after an authentication is successful.
148
+ #
149
+
150
+ def command(command)
151
+
152
+ if ! @authed
153
+ raise NetworkException.new("You must authenticate the connection successfully before sending commands.")
154
+ end
155
+
156
+ @packet = Packet::Source.new
157
+ @packet.command(command)
158
+
159
+ @socket.print @packet.to_s
160
+ rpacket = build_response_packet
161
+
162
+ if rpacket.command_type != Packet::Source::RESPONSE_NORM
163
+ raise NetworkException.new("error sending command: #{rpacket.command_type}")
164
+ end
165
+
166
+ if @return_packets
167
+ return rpacket
168
+ else
169
+ return rpacket.string1
170
+ end
171
+ end
172
+
173
+ #
174
+ # Requests authentication from the RCon server, given a
175
+ # password. Is only expected to be used once.
176
+ #
177
+
178
+ def auth(password)
179
+ establish_connection
180
+
181
+ @packet = Packet::Source.new
182
+ @packet.auth(password)
183
+
184
+ @socket.print @packet.to_s
185
+ # on auth, one junk packet is sent
186
+ rpacket = nil
187
+ 2.times { rpacket = build_response_packet }
188
+
189
+ if rpacket.command_type != Packet::Source::RESPONSE_AUTH
190
+ raise NetworkException.new("error authenticating: #{rpacket.command_type}")
191
+ end
192
+
193
+ @authed = true
194
+ if @return_packets
195
+ return rpacket
196
+ else
197
+ return true
198
+ end
199
+ end
200
+
201
+ alias_method :authenticate, :auth
202
+
203
+ #
204
+ # Disconnects from the Source server.
205
+ #
206
+
207
+ def disconnect
208
+ if @socket
209
+ @socket.close
210
+ @socket = nil
211
+ @authed = false
212
+ end
213
+ end
214
+
215
+ protected
216
+
217
+ #
218
+ # Builds a Packet::Source packet based on the response
219
+ # given by the server.
220
+ #
221
+ def build_response_packet
222
+ rpacket = Packet::Source.new
223
+ total_size = 0
224
+ request_id = 0
225
+ type = 0
226
+ response = ""
227
+ message = ""
228
+
229
+
230
+ loop do
231
+ break unless IO.select([@socket], nil, nil, 10)
232
+
233
+ #
234
+ # TODO: clean this up - read everything and then unpack.
235
+ #
236
+
237
+ tmp = @socket.recv(14)
238
+ if tmp.nil?
239
+ return nil
240
+ end
241
+ size, request_id, type, message = tmp.unpack("VVVa*")
242
+ total_size += size
243
+
244
+ # special case for authentication
245
+ break if message.sub! /\x00\x00$/, ""
246
+
247
+ response << message
248
+
249
+ # the 'size - 10' here accounts for the fact that we've snarfed 14 bytes,
250
+ # the size (which is 4 bytes) is not counted, yet represents the rest
251
+ # of the packet (which we have already taken 10 bytes from)
252
+
253
+ tmp = @socket.recv(size - 10)
254
+ response << tmp
255
+ response.sub! /\x00\x00$/, ""
256
+ end
257
+
258
+ rpacket.packet_size = total_size
259
+ rpacket.request_id = request_id
260
+ rpacket.command_type = type
261
+
262
+ # strip nulls (this is actually the end of string1 and string2)
263
+ rpacket.string1 = response.sub /\x00\x00$/, ""
264
+ return rpacket
265
+ end
266
+
267
+ # establishes a connection to the server.
268
+ def establish_connection
269
+ if @socket.nil?
270
+ @socket = TCPSocket.new(@host, @port)
271
+ end
272
+ end
273
+
274
+ end
275
+
276
+ class Minecraft < Query
277
+ # Packet::Source object that was sent as a result of the last query
278
+ attr_reader :packet
279
+ # TCPSocket object
280
+ attr_reader :socket
281
+ # Host of connection
282
+ attr_reader :host
283
+ # Port of connection
284
+ attr_reader :port
285
+ # Authentication Status
286
+ attr_reader :authed
287
+ # return full packet, or just data?
288
+ attr_accessor :return_packets
289
+
290
+ #
291
+ # Given a host and a port (dotted-quad or hostname OK), creates
292
+ # a Query::Minecraft object. Note that this will still
293
+ # require an authentication packet (see the auth() method)
294
+ # before commands can be sent.
295
+ #
296
+
297
+ def initialize(host = 'localhost', port = 25575)
298
+ @host = host
299
+ @port = port
300
+ @socket = nil
301
+ @packet = nil
302
+ @authed = false
303
+ @return_packets = false
304
+ end
305
+
306
+ #
307
+ # See Query#cvar.
308
+ #
309
+
310
+ def cvar(cvar_name)
311
+ return_packets = @return_packets
312
+ @return_packets = false
313
+ response = super
314
+ @return_packets = return_packets
315
+ return response
316
+ end
317
+
318
+ #
319
+ # Sends a RCon command to the server. May be used multiple times
320
+ # after an authentication is successful.
321
+ #
322
+
323
+ def command(command)
324
+
325
+ if ! @authed
326
+ raise NetworkException.new("You must authenticate the connection successfully before sending commands.")
327
+ end
328
+
329
+ @packet = Packet::Source.new
330
+ @packet.command(command)
331
+
332
+ @socket.print @packet.to_s
333
+ rpacket = build_response_packet
334
+
335
+ if rpacket.command_type != Packet::Source::RESPONSE_NORM
336
+ raise NetworkException.new("error sending command: #{rpacket.command_type}")
337
+ end
338
+
339
+ if @return_packets
340
+ return rpacket
341
+ else
342
+ return rpacket.string1
343
+ end
344
+ end
345
+
346
+ #
347
+ # Requests authentication from the RCon server, given a
348
+ # password. Is only expected to be used once.
349
+ #
350
+
351
+
352
+ def auth(password)
353
+ establish_connection
354
+
355
+ @packet = Packet::Source.new
356
+ @packet.auth(password)
357
+
358
+ @socket.print @packet.to_s
359
+ rpacket = nil
360
+ rpacket = build_response_packet
361
+
362
+ if rpacket.command_type != Packet::Source::RESPONSE_AUTH
363
+ raise NetworkException.new("error authenticating: #{rpacket.command_type}")
364
+ end
365
+
366
+ @authed = true
367
+ if @return_packets
368
+ return rpacket
369
+ else
370
+ return true
371
+ end
372
+ end
373
+
374
+ alias_method :authenticate, :auth
375
+
376
+ #
377
+ # Disconnects from the Minecraft server.
378
+ #
379
+
380
+ def disconnect
381
+ if @socket
382
+ @socket.close
383
+ @socket = nil
384
+ @authed = false
385
+ end
386
+ end
387
+
388
+ protected
389
+
390
+ #
391
+ # Builds a Packet::Source packet based on the response
392
+ # given by the server.
393
+ #
394
+ def build_response_packet
395
+ rpacket = Packet::Source.new
396
+ total_size = 0
397
+ request_id = 0
398
+ type = 0
399
+ response = ""
400
+ message = ""
401
+ message2 = ""
402
+
403
+ tmp = @socket.recv(4)
404
+ if tmp.nil?
405
+ return nil
406
+ end
407
+ size = tmp.unpack("V1")
408
+ tmp = @socket.recv(size[0])
409
+ request_id, type, message, message2 = tmp.unpack("V1V1a*a*")
410
+ total_size = size[0]
411
+
412
+ rpacket.packet_size = total_size
413
+ rpacket.request_id = request_id
414
+ rpacket.command_type = type
415
+
416
+ # strip nulls (this is actually the end of string1 and string2)
417
+ message.sub! /\x00\x00$/, ""
418
+ message2.sub! /\x00\x00$/, ""
419
+ rpacket.string1 = message
420
+ rpacket.string2 = message2
421
+ return rpacket
422
+ end
423
+
424
+ # establishes a connection to the server.
425
+ def establish_connection
426
+ if @socket.nil?
427
+ @socket = TCPSocket.new(@host, @port)
428
+ end
429
+ end
430
+
431
+ end
432
+
433
+ class NetworkException < Exception
434
+ end
435
+
435
436
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minecraft-query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 1.0.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Tyler Doherty
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-07-13 00:00:00.000000000 Z
12
+ date: 2013-12-12 00:00:00.000000000 Z
12
13
  dependencies: []
13
14
  description: A gem to retrieve data from a Minecraft server's query interface and
14
15
  manage a Minecraft or SRCDS server via RCON.
@@ -20,28 +21,29 @@ files:
20
21
  - lib/minecraft-query.rb
21
22
  - lib/query/query.rb
22
23
  - lib/rcon/rcon.rb
23
- homepage: http://github.com/Tylerjd/RubyMinecraft
24
+ homepage: https://github.com/Tylerjd/minecraft-query
24
25
  licenses:
25
- - New BSD License
26
- metadata: {}
26
+ - The MIT License
27
27
  post_install_message:
28
28
  rdoc_options: []
29
29
  require_paths:
30
30
  - lib
31
31
  required_ruby_version: !ruby/object:Gem::Requirement
32
+ none: false
32
33
  requirements:
33
- - - '>='
34
+ - - ! '>='
34
35
  - !ruby/object:Gem::Version
35
36
  version: '0'
36
37
  required_rubygems_version: !ruby/object:Gem::Requirement
38
+ none: false
37
39
  requirements:
38
- - - '>='
40
+ - - ! '>='
39
41
  - !ruby/object:Gem::Version
40
42
  version: '0'
41
43
  requirements: []
42
44
  rubyforge_project:
43
- rubygems_version: 2.0.3
45
+ rubygems_version: 1.8.24
44
46
  signing_key:
45
- specification_version: 4
47
+ specification_version: 3
46
48
  summary: Minecraft Querying lib
47
49
  test_files: []
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: ae62a3296fde8b1faaa34edf8059f218286fc7b9
4
- data.tar.gz: 5ebc9220d5877cc7d82dfb544ca1c21b4c14b0c5
5
- SHA512:
6
- metadata.gz: a73065a84b2d889e93573829b19b038394fd60da63e5c82e0d53576d67e2fa9afc68235010d80728715770e6104f4ddb17218fe5751a2926a08f1fdad78905d4
7
- data.tar.gz: eda0efe74b4ce5df2991d82f182f702b9912b89a7f87c3a8b3888d6d1c280eea88ca65fb45ace7e01ea93479662e383961965e6e25c5adb2fa5972a1a7ca8d95