minecraft-query 0.1.3 → 1.0.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.
@@ -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