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.
- data/lib/minecraft-query.rb +24 -24
- data/lib/query/query.rb +86 -83
- data/lib/rcon/rcon.rb +435 -434
- metadata +11 -9
- checksums.yaml +0 -7
data/lib/minecraft-query.rb
CHANGED
@@ -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
|
data/lib/query/query.rb
CHANGED
@@ -1,90 +1,93 @@
|
|
1
1
|
class Query
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
90
|
-
end
|
93
|
+
end
|
data/lib/rcon/rcon.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
match =
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
@
|
59
|
-
@
|
60
|
-
@
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
@
|
75
|
-
@
|
76
|
-
@
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
#
|
118
|
-
# a
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@
|
126
|
-
@
|
127
|
-
@
|
128
|
-
@
|
129
|
-
@
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
#
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
@packet.
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
@packet.
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
#
|
204
|
-
#
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
@socket
|
210
|
-
@
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
#
|
218
|
-
#
|
219
|
-
#
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
#
|
234
|
-
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
# the size
|
250
|
-
#
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
response
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
rpacket.
|
259
|
-
rpacket.
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
#
|
291
|
-
# a
|
292
|
-
#
|
293
|
-
#
|
294
|
-
#
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
@
|
299
|
-
@
|
300
|
-
@
|
301
|
-
@
|
302
|
-
@
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
#
|
307
|
-
#
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
#
|
319
|
-
#
|
320
|
-
#
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
@packet.
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
#
|
347
|
-
#
|
348
|
-
#
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
@packet.
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
rpacket =
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
#
|
377
|
-
#
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
@socket
|
383
|
-
@
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
#
|
391
|
-
#
|
392
|
-
#
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
rpacket.
|
413
|
-
rpacket.
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
rpacket.
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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.
|
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-
|
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:
|
24
|
+
homepage: https://github.com/Tylerjd/minecraft-query
|
24
25
|
licenses:
|
25
|
-
-
|
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:
|
45
|
+
rubygems_version: 1.8.24
|
44
46
|
signing_key:
|
45
|
-
specification_version:
|
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
|