minecraft-query-v2 2.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: daa1446115224aca2cd52edad125f434b9d637ab
4
+ data.tar.gz: f2fdc5dd429483ea504820643c471d1c08b90201
5
+ SHA512:
6
+ metadata.gz: b33861b203b4f4d91767587529f18432d26fd89545889b38a84801aa4970a34c53ea400c4cc1ebb2bb6d079649850b3971f8e90df2208bc8136e07645660e549
7
+ data.tar.gz: c4e1b2875eed5172569c7b9e5133fad415de2cf09848bf5b7d9d0a490ed9520bae21e0a208998b8756bebe05cadc8e2696245a691b2d2dbfbd606e0b12bdb4d8
@@ -0,0 +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 = MCQuery::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
+ end
@@ -0,0 +1,96 @@
1
+ class MCQuery
2
+ require 'timeout'
3
+
4
+ def self.init
5
+ @sock = UDPSocket.new
6
+ @sock.connect(@addr,@port)
7
+ @val = {}
8
+ @buff = nil
9
+ key
10
+ end
11
+
12
+ def self.key
13
+ begin
14
+ Timeout.timeout(1) do
15
+ start = @sock.send("\xFE\xFD\x09\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT), 0)
16
+ t = @sock.recvfrom(1460)[0]
17
+ key = t[5...-1].to_i
18
+ @key = Array(key).pack('N')
19
+ end
20
+ rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
21
+ return "Host unreachable, check your configuration and try again"
22
+ return "An Exception occured, please check last message"
23
+ rescue StandardError => e
24
+ return "An other error occured. Check your ruby installation and tell the tech monkey this:"
25
+ return e
26
+ end
27
+ end
28
+
29
+ def self.simpleQuery(addr = 'localhost', port = 25565)
30
+ @addr = addr
31
+ @port = port
32
+ init
33
+ begin
34
+ Timeout.timeout(1) do
35
+ query = @sock.send("\xFE\xFD\x00\x01\x02\x03\x04".force_encoding(Encoding::ASCII_8BIT) + @key.to_s, 0)
36
+ data = @sock.recvfrom(1460)[0]
37
+ buffer = data[5...-1]
38
+ @val[:motd], @val[:gametype], @val[:map], @val[:numplayers], @val[:maxplayers], @buf = buffer.split("\x00", 6)
39
+ if @sock != nil
40
+ @sock.close
41
+ end
42
+ end
43
+ return @val
44
+ rescue StandardError => e
45
+ return e
46
+ end
47
+ end
48
+
49
+ def self.fullQuery(addr = 'localhost', port = 25565)
50
+ @addr = addr
51
+ @port = port
52
+ init
53
+ begin
54
+ Timeout.timeout(1) do
55
+ 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)
56
+ data = @sock.recvfrom(1460)[0]
57
+ buffer = data[11...-1]
58
+ items, players = buffer.split("\x00\x00\x01player_\x00\x00".force_encoding(Encoding::ASCII_8BIT))
59
+ if items[0...8] == 'hostname'
60
+ items = 'motd' + items[8...-1]
61
+ end
62
+ vals = {}
63
+ items = items.split("\x00")
64
+ items.each_with_index do |key, idx|
65
+ next unless idx % 2 == 0
66
+ vals[key] = items[idx + 1]
67
+ end
68
+
69
+ vals["motd"] = vals["hostname"]
70
+ vals.delete("hostname")
71
+ vals.delete("um") if vals["um"]
72
+
73
+ players = players[0..-2] if players
74
+ if players
75
+ vals[:players] = players.split("\x00")
76
+ end
77
+ puts vals
78
+ vals["raw_plugins"] = vals["plugins"]
79
+ parts = vals["raw_plugins"].split(":")
80
+ server = parts[0].strip() if parts[0]
81
+ plugins = []
82
+ if parts.size == 2
83
+ plugins = parts[1].split(";").map {|value| value.strip() }
84
+ end
85
+ vals["plugins"] = plugins
86
+ vals["server"] = server
87
+ vals["timestamp"] = Time.now
88
+ return vals.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
89
+ end
90
+ rescue StandardError => e
91
+ puts e
92
+ #return "An other error occured. Check your minecraft config and tell the tech monkey this:"
93
+ raise e
94
+ end
95
+ end
96
+ end
@@ -0,0 +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
+ # 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
+
436
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minecraft-query-v2
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - MV Code Club
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A gem to retrieve data from a Minecraft server's query interface and
14
+ manage a Minecraft or SRCDS server via RCON.
15
+ email: info@mvcodeclub.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/minecraft-query.rb
21
+ - lib/query/query.rb
22
+ - lib/rcon/rcon.rb
23
+ homepage: https://github.com/mvcodeclub/minecraft-query
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.5.1
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Minecraft Querying lib
47
+ test_files: []