minestat 2.1.1 → 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 +139 -35
- metadata +3 -4
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
|
@@ -24,7 +24,7 @@ require 'timeout'
|
|
24
24
|
# Provides a ruby interface for polling Minecraft server status.
|
25
25
|
class MineStat
|
26
26
|
# MineStat version
|
27
|
-
VERSION = "2.
|
27
|
+
VERSION = "2.2.0"
|
28
28
|
# Number of values expected from server
|
29
29
|
NUM_FIELDS = 6
|
30
30
|
# Number of values expected from a 1.8b/1.3 server
|
@@ -33,8 +33,17 @@ class MineStat
|
|
33
33
|
MAX_VARINT_SIZE = 5
|
34
34
|
# Default TCP port
|
35
35
|
DEFAULT_PORT = 25565
|
36
|
-
#
|
36
|
+
# Bedrock/Pocket Edition default UDP port
|
37
|
+
DEFAULT_BEDROCK_PORT = 19132
|
38
|
+
# Default TCP/UDP timeout in seconds
|
37
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
|
38
47
|
|
39
48
|
##
|
40
49
|
# Stores constants that represent the results of a server ping
|
@@ -64,25 +73,28 @@ class MineStat
|
|
64
73
|
EXTENDED = 2
|
65
74
|
# Server versions 1.7 to latest
|
66
75
|
JSON = 3
|
76
|
+
# Bedrock/Pocket Edition
|
77
|
+
BEDROCK = 4
|
67
78
|
end
|
68
79
|
|
69
80
|
##
|
70
81
|
# Instantiate an instance of MineStat and poll the specified server for information
|
71
82
|
def initialize(address, port = DEFAULT_PORT, timeout = DEFAULT_TIMEOUT, request_type = Request::NONE)
|
72
|
-
@address = address
|
73
|
-
@port = port
|
74
|
-
@online
|
75
|
-
@version
|
76
|
-
@
|
77
|
-
@
|
78
|
-
@
|
79
|
-
@
|
80
|
-
@
|
81
|
-
@
|
82
|
-
@
|
83
|
-
@
|
84
|
-
@
|
85
|
-
@
|
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
|
86
98
|
|
87
99
|
case request_type
|
88
100
|
when Request::BETA
|
@@ -93,13 +105,14 @@ class MineStat
|
|
93
105
|
retval = extended_legacy_request()
|
94
106
|
when Request::JSON
|
95
107
|
retval = json_request()
|
108
|
+
when Request::BEDROCK
|
109
|
+
retval = bedrock_request()
|
96
110
|
else
|
97
|
-
# Attempt various SLP ping requests in a particular order. If the
|
98
|
-
#
|
99
|
-
#
|
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
|
100
114
|
# however since it may be due to an issue during the handshake.
|
101
115
|
# Note: Newer server versions may still respond to older SLP requests.
|
102
|
-
# For example, 1.13.2 responds to 1.4/1.5 queries, but not 1.6 queries.
|
103
116
|
# SLP 1.4/1.5
|
104
117
|
retval = legacy_request()
|
105
118
|
# SLP 1.8b/1.3
|
@@ -114,6 +127,10 @@ class MineStat
|
|
114
127
|
unless retval == Retval::CONNFAIL
|
115
128
|
retval = json_request()
|
116
129
|
end
|
130
|
+
# Bedrock
|
131
|
+
unless retval == Retval::CONNFAIL
|
132
|
+
retval = bedrock_request()
|
133
|
+
end
|
117
134
|
end
|
118
135
|
@online = false unless retval == Retval::SUCCESS
|
119
136
|
end
|
@@ -133,6 +150,7 @@ class MineStat
|
|
133
150
|
end
|
134
151
|
end
|
135
152
|
end
|
153
|
+
@stripped_motd = @stripped_motd.force_encoding('UTF-8')
|
136
154
|
@stripped_motd = @stripped_motd.gsub(/§./, "")
|
137
155
|
end
|
138
156
|
|
@@ -140,8 +158,15 @@ class MineStat
|
|
140
158
|
# Establishes a connection to the Minecraft server
|
141
159
|
def connect()
|
142
160
|
begin
|
143
|
-
|
144
|
-
|
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
|
145
170
|
@latency = ((Time.now - start_time) * 1000).round
|
146
171
|
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
147
172
|
return Retval::CONNFAIL
|
@@ -156,15 +181,27 @@ class MineStat
|
|
156
181
|
def parse_data(delimiter, is_beta = false)
|
157
182
|
data = nil
|
158
183
|
begin
|
159
|
-
if @
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
166
202
|
end
|
167
|
-
rescue
|
203
|
+
rescue
|
204
|
+
#rescue => exception
|
168
205
|
#$stderr.puts exception
|
169
206
|
return Retval::UNKNOWN
|
170
207
|
end
|
@@ -185,7 +222,20 @@ class MineStat
|
|
185
222
|
else
|
186
223
|
return Retval::UNKNOWN
|
187
224
|
end
|
188
|
-
|
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
|
189
239
|
if server_info != nil && server_info.length >= NUM_FIELDS
|
190
240
|
# server_info[0] contains the section symbol and 1
|
191
241
|
@protocol = server_info[1].to_i # contains the protocol version (51 for 1.9 or 78 for 1.6.4 for example)
|
@@ -424,6 +474,57 @@ class MineStat
|
|
424
474
|
return vint
|
425
475
|
end
|
426
476
|
|
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
|
+
|
427
528
|
# Returns the Minecraft server IP
|
428
529
|
attr_reader :address
|
429
530
|
|
@@ -436,12 +537,15 @@ class MineStat
|
|
436
537
|
# Returns the Minecraft version that the server is running
|
437
538
|
attr_reader :version
|
438
539
|
|
439
|
-
# Returns the
|
540
|
+
# Returns the game mode (Bedrock/Pocket Edition only)
|
541
|
+
attr_reader :mode
|
542
|
+
|
543
|
+
# Returns the full version of the MotD
|
440
544
|
#
|
441
|
-
# If you just want the
|
545
|
+
# If you just want the MotD text, use stripped_motd
|
442
546
|
attr_reader :motd
|
443
547
|
|
444
|
-
# Returns just the plain text contained within the
|
548
|
+
# Returns just the plain text contained within the MotD
|
445
549
|
attr_reader :stripped_motd
|
446
550
|
|
447
551
|
# Returns the current player count
|
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
|
-
- Stepan Melnikov
|
9
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.
|
@@ -39,7 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '0'
|
41
40
|
requirements: []
|
42
|
-
rubygems_version: 3.
|
41
|
+
rubygems_version: 3.3.7
|
43
42
|
signing_key:
|
44
43
|
specification_version: 4
|
45
44
|
summary: Minecraft server status checker
|