minestat 2.0.0 → 2.2.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.
- checksums.yaml +4 -4
- data/lib/minestat.rb +258 -50
- metadata +6 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 289592d2813eb1246b9a3033ce10a219513bf0c9cb792951051bdf6f97b20598
|
4
|
+
data.tar.gz: beee739e9c19250fb6663e9561e065dfbcbae817442afe07ca3a21911e5f975a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad094493fac2c5b788c7ed86bdcc1e5296715d410cfe435491101fa56aae2bda6cd81644a88ce8caf183e6ae13072e559a073a4a4b42f9a51b0dfe261a46b378
|
7
|
+
data.tar.gz: a757852cab9404fe9937a9a4f9fb7bac2effccd3317173d2abf0bc200a87dea5a6576e382c878be9d15f31b2489298197fafa6f5c1b4eaa1aff75d90f6ab6c16
|
data/lib/minestat.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# minestat.rb - A Minecraft server status checker
|
2
|
-
# Copyright (C) 2014-
|
2
|
+
# Copyright (C) 2014-2022 Lloyd Dilley
|
3
3
|
# http://www.dilley.me/
|
4
4
|
#
|
5
5
|
# This program is free software; you can redistribute it and/or modify
|
@@ -20,43 +20,81 @@ require 'json'
|
|
20
20
|
require 'socket'
|
21
21
|
require 'timeout'
|
22
22
|
|
23
|
+
##
|
24
|
+
# Provides a ruby interface for polling Minecraft server status.
|
23
25
|
class MineStat
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
# MineStat version
|
27
|
+
VERSION = "2.2.0"
|
28
|
+
# Number of values expected from server
|
29
|
+
NUM_FIELDS = 6
|
30
|
+
# Number of values expected from a 1.8b/1.3 server
|
31
|
+
NUM_FIELDS_BETA = 3
|
32
|
+
# Maximum number of bytes a varint can be
|
33
|
+
MAX_VARINT_SIZE = 5
|
34
|
+
# Default TCP port
|
35
|
+
DEFAULT_PORT = 25565
|
36
|
+
# Bedrock/Pocket Edition default UDP port
|
37
|
+
DEFAULT_BEDROCK_PORT = 19132
|
38
|
+
# Default TCP/UDP timeout in seconds
|
39
|
+
DEFAULT_TIMEOUT = 5
|
40
|
+
# Bedrock/Pocket Edition packet offset in bytes (1 + 8 + 8 + 16 + 2)
|
41
|
+
# Unconnected pong (0x1C) = 1 byte
|
42
|
+
# Timestamp as a long = 8 bytes
|
43
|
+
# Server GUID as a long = 8 bytes
|
44
|
+
# Magic number = 16 bytes
|
45
|
+
# String ID length = 2 bytes
|
46
|
+
BEDROCK_PACKET_OFFSET = 35
|
30
47
|
|
48
|
+
##
|
49
|
+
# Stores constants that represent the results of a server ping
|
31
50
|
module Retval
|
51
|
+
# The server ping completed successfully
|
32
52
|
SUCCESS = 0
|
53
|
+
# The server ping failed due to a connection error
|
33
54
|
CONNFAIL = -1
|
55
|
+
# The server ping failed due to a connection time out
|
34
56
|
TIMEOUT = -2
|
57
|
+
# The server ping failed for an unknown reason
|
35
58
|
UNKNOWN = -3
|
36
59
|
end
|
37
60
|
|
61
|
+
##
|
62
|
+
# Stores constants that represent the different kinds of server
|
63
|
+
# list pings/requests that a Minecraft server might expect when
|
64
|
+
# being polled for status information.
|
38
65
|
module Request
|
66
|
+
# Try everything
|
39
67
|
NONE = -1
|
68
|
+
# Server versions 1.8b to 1.3
|
40
69
|
BETA = 0
|
70
|
+
# Server versions 1.4 to 1.5
|
41
71
|
LEGACY = 1
|
72
|
+
# Server version 1.6
|
42
73
|
EXTENDED = 2
|
74
|
+
# Server versions 1.7 to latest
|
43
75
|
JSON = 3
|
76
|
+
# Bedrock/Pocket Edition
|
77
|
+
BEDROCK = 4
|
44
78
|
end
|
45
79
|
|
80
|
+
##
|
81
|
+
# Instantiate an instance of MineStat and poll the specified server for information
|
46
82
|
def initialize(address, port = DEFAULT_PORT, timeout = DEFAULT_TIMEOUT, request_type = Request::NONE)
|
47
|
-
@address = address
|
48
|
-
@port = port
|
49
|
-
@online
|
50
|
-
@version
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@
|
54
|
-
@
|
55
|
-
@
|
56
|
-
@
|
57
|
-
@
|
58
|
-
@
|
59
|
-
@
|
83
|
+
@address = address # address of server
|
84
|
+
@port = port # TCP/UDP port of server
|
85
|
+
@online # online or offline?
|
86
|
+
@version # server version
|
87
|
+
@mode = "Unspecified" # game mode (Bedrock/Pocket Edition only)
|
88
|
+
@motd # message of the day
|
89
|
+
@stripped_motd # message of the day without formatting
|
90
|
+
@current_players # current number of players online
|
91
|
+
@max_players # maximum player capacity
|
92
|
+
@protocol # protocol level
|
93
|
+
@json_data # JSON data for 1.7 queries
|
94
|
+
@latency # ping time to server in milliseconds
|
95
|
+
@timeout = timeout # TCP/UDP timeout
|
96
|
+
@server # server socket
|
97
|
+
@request_type # Protocol version
|
60
98
|
|
61
99
|
case request_type
|
62
100
|
when Request::BETA
|
@@ -67,13 +105,14 @@ class MineStat
|
|
67
105
|
retval = extended_legacy_request()
|
68
106
|
when Request::JSON
|
69
107
|
retval = json_request()
|
108
|
+
when Request::BEDROCK
|
109
|
+
retval = bedrock_request()
|
70
110
|
else
|
71
|
-
# Attempt various SLP ping requests in a particular order. If the
|
72
|
-
#
|
73
|
-
#
|
111
|
+
# Attempt various SLP ping requests in a particular order. If the
|
112
|
+
# connection fails, there is no reason to continue with subsequent
|
113
|
+
# requests. Attempts should continue in the event of a timeout
|
74
114
|
# however since it may be due to an issue during the handshake.
|
75
115
|
# Note: Newer server versions may still respond to older SLP requests.
|
76
|
-
# For example, 1.13.2 responds to 1.4/1.5 queries, but not 1.6 queries.
|
77
116
|
# SLP 1.4/1.5
|
78
117
|
retval = legacy_request()
|
79
118
|
# SLP 1.8b/1.3
|
@@ -81,22 +120,53 @@ class MineStat
|
|
81
120
|
retval = beta_request()
|
82
121
|
end
|
83
122
|
# SLP 1.6
|
84
|
-
unless retval == Retval::
|
123
|
+
unless retval == Retval::CONNFAIL
|
85
124
|
retval = extended_legacy_request()
|
86
125
|
end
|
87
126
|
# SLP 1.7
|
88
|
-
unless retval == Retval::
|
127
|
+
unless retval == Retval::CONNFAIL
|
89
128
|
retval = json_request()
|
90
129
|
end
|
130
|
+
# Bedrock
|
131
|
+
unless retval == Retval::CONNFAIL
|
132
|
+
retval = bedrock_request()
|
133
|
+
end
|
91
134
|
end
|
92
135
|
@online = false unless retval == Retval::SUCCESS
|
93
136
|
end
|
94
137
|
|
95
|
-
#
|
138
|
+
# Strips message of the day formatting characters
|
139
|
+
def strip_motd()
|
140
|
+
unless @motd['text'] == nil
|
141
|
+
@stripped_motd = @motd['text']
|
142
|
+
else
|
143
|
+
@stripped_motd = @motd
|
144
|
+
end
|
145
|
+
unless @motd['extra'] == nil
|
146
|
+
json_data = @motd['extra']
|
147
|
+
unless json_data.nil? || json_data.empty?
|
148
|
+
json_data.each do |nested_hash|
|
149
|
+
@stripped_motd += nested_hash['text']
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
@stripped_motd = @stripped_motd.force_encoding('UTF-8')
|
154
|
+
@stripped_motd = @stripped_motd.gsub(/§./, "")
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Establishes a connection to the Minecraft server
|
96
159
|
def connect()
|
97
160
|
begin
|
98
|
-
|
99
|
-
|
161
|
+
if @request_type == Request::BEDROCK || @request_type == "Bedrock/Pocket Edition"
|
162
|
+
@port = DEFAULT_BEDROCK_PORT if @port == DEFAULT_PORT && @request_type == Request::NONE
|
163
|
+
start_time = Time.now
|
164
|
+
@server = UDPSocket.new
|
165
|
+
@server.connect(@address, @port)
|
166
|
+
else
|
167
|
+
start_time = Time.now
|
168
|
+
@server = TCPSocket.new(@address, @port)
|
169
|
+
end
|
100
170
|
@latency = ((Time.now - start_time) * 1000).round
|
101
171
|
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
102
172
|
return Retval::CONNFAIL
|
@@ -111,16 +181,28 @@ class MineStat
|
|
111
181
|
def parse_data(delimiter, is_beta = false)
|
112
182
|
data = nil
|
113
183
|
begin
|
114
|
-
if @
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
184
|
+
if @request_type == "Bedrock/Pocket Edition"
|
185
|
+
if @server.recv(1, Socket::MSG_PEEK).unpack('C').first == 0x1C # unconnected pong packet
|
186
|
+
server_id_len = @server.recv(BEDROCK_PACKET_OFFSET, Socket::MSG_PEEK)[-2..-1].unpack('n').first
|
187
|
+
data = @server.recv(BEDROCK_PACKET_OFFSET + server_id_len)[BEDROCK_PACKET_OFFSET..-1]
|
188
|
+
@server.close
|
189
|
+
else
|
190
|
+
@server.close
|
191
|
+
return Retval::UNKNOWN
|
192
|
+
end
|
193
|
+
else # SLP
|
194
|
+
if @server.read(1).unpack('C').first == 0xFF # kick packet (255)
|
195
|
+
len = @server.read(2).unpack('n').first
|
196
|
+
data = @server.read(len * 2).force_encoding('UTF-16BE').encode('UTF-8')
|
197
|
+
@server.close
|
198
|
+
else
|
199
|
+
@server.close
|
200
|
+
return Retval::UNKNOWN
|
201
|
+
end
|
121
202
|
end
|
122
|
-
rescue
|
123
|
-
|
203
|
+
rescue
|
204
|
+
#rescue => exception
|
205
|
+
#$stderr.puts exception
|
124
206
|
return Retval::UNKNOWN
|
125
207
|
end
|
126
208
|
|
@@ -133,18 +215,33 @@ class MineStat
|
|
133
215
|
if server_info != nil && server_info.length >= NUM_FIELDS_BETA
|
134
216
|
@version = ">=1.8b/1.3" # since server does not return version, set it
|
135
217
|
@motd = server_info[0]
|
218
|
+
strip_motd
|
136
219
|
@current_players = server_info[1].to_i
|
137
220
|
@max_players = server_info[2].to_i
|
138
221
|
@online = true
|
139
222
|
else
|
140
223
|
return Retval::UNKNOWN
|
141
224
|
end
|
142
|
-
|
225
|
+
elsif @request_type == "Bedrock/Pocket Edition"
|
226
|
+
if server_info != nil
|
227
|
+
@protocol = server_info[2].to_i
|
228
|
+
@version = "#{server_info[3]} #{server_info[7]} (#{server_info[0]})"
|
229
|
+
@mode = server_info[8]
|
230
|
+
@motd = server_info[1]
|
231
|
+
strip_motd
|
232
|
+
@current_players = server_info[4].to_i
|
233
|
+
@max_players = server_info[5].to_i
|
234
|
+
@online = true
|
235
|
+
else
|
236
|
+
return Retval::UNKNOWN
|
237
|
+
end
|
238
|
+
else # SLP
|
143
239
|
if server_info != nil && server_info.length >= NUM_FIELDS
|
144
240
|
# server_info[0] contains the section symbol and 1
|
145
241
|
@protocol = server_info[1].to_i # contains the protocol version (51 for 1.9 or 78 for 1.6.4 for example)
|
146
242
|
@version = server_info[2]
|
147
243
|
@motd = server_info[3]
|
244
|
+
strip_motd
|
148
245
|
@current_players = server_info[4].to_i
|
149
246
|
@max_players = server_info[5].to_i
|
150
247
|
@online = true
|
@@ -155,14 +252,17 @@ class MineStat
|
|
155
252
|
return Retval::SUCCESS
|
156
253
|
end
|
157
254
|
|
158
|
-
|
255
|
+
##
|
159
256
|
# 1.8 beta through 1.3 servers communicate as follows for a ping request:
|
160
257
|
# 1. Client sends \xFE (server list ping)
|
161
258
|
# 2. Server responds with:
|
162
259
|
# 2a. \xFF (kick packet)
|
163
260
|
# 2b. data length
|
164
261
|
# 2c. 3 fields delimited by \u00A7 (section symbol)
|
165
|
-
# The 3 fields, in order, are:
|
262
|
+
# The 3 fields, in order, are:
|
263
|
+
# * message of the day
|
264
|
+
# * current players
|
265
|
+
# * max players
|
166
266
|
def beta_request()
|
167
267
|
retval = nil
|
168
268
|
begin
|
@@ -183,7 +283,7 @@ class MineStat
|
|
183
283
|
return retval
|
184
284
|
end
|
185
285
|
|
186
|
-
|
286
|
+
##
|
187
287
|
# 1.4 and 1.5 servers communicate as follows for a ping request:
|
188
288
|
# 1. Client sends:
|
189
289
|
# 1a. \xFE (server list ping)
|
@@ -192,8 +292,14 @@ class MineStat
|
|
192
292
|
# 2a. \xFF (kick packet)
|
193
293
|
# 2b. data length
|
194
294
|
# 2c. 6 fields delimited by \x00 (null)
|
195
|
-
# The 6 fields, in order, are:
|
196
|
-
#
|
295
|
+
# The 6 fields, in order, are:
|
296
|
+
# * the section symbol and 1
|
297
|
+
# * protocol version
|
298
|
+
# * server version
|
299
|
+
# * message of the day
|
300
|
+
# * current players
|
301
|
+
# * max players
|
302
|
+
#
|
197
303
|
# The protocol version corresponds with the server version and can be the
|
198
304
|
# same for different server versions.
|
199
305
|
def legacy_request()
|
@@ -216,7 +322,7 @@ class MineStat
|
|
216
322
|
return retval
|
217
323
|
end
|
218
324
|
|
219
|
-
|
325
|
+
##
|
220
326
|
# 1.6 servers communicate as follows for a ping request:
|
221
327
|
# 1. Client sends:
|
222
328
|
# 1a. \xFE (server list ping)
|
@@ -233,8 +339,14 @@ class MineStat
|
|
233
339
|
# 2a. \xFF (kick packet)
|
234
340
|
# 2b. data length
|
235
341
|
# 2c. 6 fields delimited by \x00 (null)
|
236
|
-
# The 6 fields, in order, are:
|
237
|
-
#
|
342
|
+
# The 6 fields, in order, are:
|
343
|
+
# * the section symbol and 1
|
344
|
+
# * protocol version
|
345
|
+
# * server version
|
346
|
+
# * message of the day
|
347
|
+
# * current players
|
348
|
+
# * max players
|
349
|
+
#
|
238
350
|
# The protocol version corresponds with the server version and can be the
|
239
351
|
# same for different server versions.
|
240
352
|
def extended_legacy_request()
|
@@ -264,7 +376,7 @@ class MineStat
|
|
264
376
|
return retval
|
265
377
|
end
|
266
378
|
|
267
|
-
|
379
|
+
##
|
268
380
|
# 1.7 to current servers communicate as follows for a ping request:
|
269
381
|
# 1. Client sends:
|
270
382
|
# 1a. \x00 (handshake packet containing the fields specified below)
|
@@ -309,7 +421,8 @@ class MineStat
|
|
309
421
|
@json_data = json_data
|
310
422
|
@protocol = json_data['version']['protocol'].to_i
|
311
423
|
@version = json_data['version']['name']
|
312
|
-
@motd = json_data['description']
|
424
|
+
@motd = json_data['description']
|
425
|
+
strip_motd
|
313
426
|
@current_players = json_data['players']['online'].to_i
|
314
427
|
@max_players = json_data['players']['max'].to_i
|
315
428
|
if !@version.empty? && !@motd.empty? && !@current_players.nil? && !@max_players.nil?
|
@@ -361,5 +474,100 @@ class MineStat
|
|
361
474
|
return vint
|
362
475
|
end
|
363
476
|
|
364
|
-
|
477
|
+
##
|
478
|
+
# Bedrock/Pocket Edition servers communicate as follows for an unconnected ping request:
|
479
|
+
# 1. Client sends:
|
480
|
+
# 1a. \x01 (unconnected ping packet containing the fields specified below)
|
481
|
+
# 1b. current time as a long
|
482
|
+
# 1c. magic number
|
483
|
+
# 1d. client GUID as a long
|
484
|
+
# 2. Server responds with:
|
485
|
+
# 2a. \x1c (unconnected pong packet containing the follow fields)
|
486
|
+
# 2b. current time as a long
|
487
|
+
# 2c. server GUID as a long
|
488
|
+
# 2d. 16-bit magic number
|
489
|
+
# 2e. server ID as a string
|
490
|
+
# The fields from the pong response, in order, are:
|
491
|
+
# * edition
|
492
|
+
# * MotD line 1
|
493
|
+
# * protocol version
|
494
|
+
# * version name
|
495
|
+
# * current player count
|
496
|
+
# * maximum player count
|
497
|
+
# * unique server ID
|
498
|
+
# * MotD line 2
|
499
|
+
# * game mode as a string
|
500
|
+
# * game mode as a numeric
|
501
|
+
# * IPv4 port number
|
502
|
+
# * IPv6 port number
|
503
|
+
def bedrock_request()
|
504
|
+
retval = nil
|
505
|
+
begin
|
506
|
+
Timeout::timeout(@timeout) do
|
507
|
+
@request_type = "Bedrock/Pocket Edition"
|
508
|
+
retval = connect()
|
509
|
+
return retval unless retval == Retval::SUCCESS
|
510
|
+
# Perform handshake and acquire data
|
511
|
+
payload = "\x01".force_encoding('ASCII-8BIT') # unconnected ping
|
512
|
+
payload += [Time.now.to_i].pack('L!<').force_encoding('ASCII-8BIT') # current time as a long
|
513
|
+
payload += "\x00\xFF\xFF\x00\xFE\xFE\xFE\xFE\xFD\xFD\xFD\xFD\x12\x34\x56\x78".force_encoding('ASCII-8BIT') # magic number
|
514
|
+
payload += [2].pack('L!<').force_encoding('ASCII-8BIT') # client GUID as a long
|
515
|
+
@server.write(payload)
|
516
|
+
@server.flush
|
517
|
+
retval = parse_data("\x3B") # semicolon
|
518
|
+
end
|
519
|
+
rescue Timeout::Error
|
520
|
+
return Retval::TIMEOUT
|
521
|
+
rescue => exception
|
522
|
+
$stderr.puts exception
|
523
|
+
return Retval::UNKNOWN
|
524
|
+
end
|
525
|
+
return retval
|
526
|
+
end
|
527
|
+
|
528
|
+
# Returns the Minecraft server IP
|
529
|
+
attr_reader :address
|
530
|
+
|
531
|
+
# Returns the Minecraft server TCP port
|
532
|
+
attr_reader :port
|
533
|
+
|
534
|
+
# Returns a boolean describing whether the server is online or offline
|
535
|
+
attr_reader :online
|
536
|
+
|
537
|
+
# Returns the Minecraft version that the server is running
|
538
|
+
attr_reader :version
|
539
|
+
|
540
|
+
# Returns the game mode (Bedrock/Pocket Edition only)
|
541
|
+
attr_reader :mode
|
542
|
+
|
543
|
+
# Returns the full version of the MotD
|
544
|
+
#
|
545
|
+
# If you just want the MotD text, use stripped_motd
|
546
|
+
attr_reader :motd
|
547
|
+
|
548
|
+
# Returns just the plain text contained within the MotD
|
549
|
+
attr_reader :stripped_motd
|
550
|
+
|
551
|
+
# Returns the current player count
|
552
|
+
attr_reader :current_players
|
553
|
+
|
554
|
+
# Returns the maximum player count
|
555
|
+
attr_reader :max_players
|
556
|
+
|
557
|
+
# Returns the SLP (Server List Ping) protocol level
|
558
|
+
#
|
559
|
+
# This is arbitrary and varies by Minecraft version.
|
560
|
+
# However, multiple Minecraft versions can share the same
|
561
|
+
# protocol level
|
562
|
+
attr_reader :protocol
|
563
|
+
|
564
|
+
# Returns the complete JSON response data for queries to Minecraft
|
565
|
+
# servers with a version greater than or equal to 1.7
|
566
|
+
attr_reader :json_data
|
567
|
+
|
568
|
+
# Returns the ping time to the server in ms
|
569
|
+
attr_reader :latency
|
570
|
+
|
571
|
+
# Returns the SLP (Server List Ping) protocol version
|
572
|
+
attr_reader :request_type
|
365
573
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minestat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lloyd Dilley
|
8
|
-
|
9
|
-
autorequire:
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2022-05-01 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: MineStat polls Minecraft server data such as version, motd, current players,
|
15
14
|
and max players.
|
@@ -24,7 +23,7 @@ homepage: https://github.com/FragLand/minestat
|
|
24
23
|
licenses:
|
25
24
|
- GPL-3.0
|
26
25
|
metadata: {}
|
27
|
-
post_install_message:
|
26
|
+
post_install_message:
|
28
27
|
rdoc_options: []
|
29
28
|
require_paths:
|
30
29
|
- lib
|
@@ -39,9 +38,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '0'
|
41
40
|
requirements: []
|
42
|
-
|
43
|
-
|
44
|
-
signing_key:
|
41
|
+
rubygems_version: 3.3.7
|
42
|
+
signing_key:
|
45
43
|
specification_version: 4
|
46
44
|
summary: Minecraft server status checker
|
47
45
|
test_files: []
|