minestat 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/minestat.rb +230 -28
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 20bcda7c193a48fa96c641630b9a9c732c7b0042d711732c8c6a60774ea425e5
|
4
|
+
data.tar.gz: 39fe195c29631131ee3003d0931658556395efa64c1513cbaa28acde0240fea6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59a25b2226f196216bffe13ba50f3ae4716c9110034bf1db23064fa0ea2b80436fd3e42a996a46a71e7a7041da0edd52767a0df92285b15a54a325545ee4d8f3
|
7
|
+
data.tar.gz: 8a18ab5dfecee0d7fc7adeb7b6e63c16165d499db8f679a27ad63833e082fffe52f040b482009b0941750f7eee819bb59ad3f24d8c924d2aef9b339488b83176
|
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 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,48 +20,250 @@ require 'socket'
|
|
20
20
|
require 'timeout'
|
21
21
|
|
22
22
|
class MineStat
|
23
|
-
NUM_FIELDS = 6
|
23
|
+
NUM_FIELDS = 6 # number of values expected from server
|
24
|
+
NUM_FIELDS_BETA = 3 # number of values expected from a 1.8b/1.3 server
|
25
|
+
DEFAULT_TIMEOUT = 5 # default TCP timeout in seconds
|
24
26
|
|
25
|
-
|
27
|
+
module Retval
|
28
|
+
SUCCESS = 0
|
29
|
+
CONNFAIL = -1
|
30
|
+
TIMEOUT = -2
|
31
|
+
UNKNOWN = -3
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(address, port, timeout = DEFAULT_TIMEOUT)
|
26
35
|
@address = address
|
27
36
|
@port = port
|
28
|
-
@online
|
29
|
-
@version
|
30
|
-
@motd
|
31
|
-
@current_players
|
32
|
-
@max_players
|
37
|
+
@online # online or offline?
|
38
|
+
@version # server version
|
39
|
+
@motd # message of the day
|
40
|
+
@current_players # current number of players online
|
41
|
+
@max_players # maximum player capacity
|
42
|
+
@latency # ping time to server in milliseconds
|
43
|
+
|
44
|
+
# Try the newest protocol first and work down. If the query succeeds or the
|
45
|
+
# connection fails, there is no reason to continue with subsequent queries.
|
46
|
+
# Attempts should continue in the event of a timeout however since it may
|
47
|
+
# be due to an issue during the handshake.
|
48
|
+
# Note: Newer server versions may still respond to older ping query types.
|
49
|
+
# For example, 1.13.2 responds to 1.4/1.5 queries, but not 1.6 queries.
|
50
|
+
# 1.7
|
51
|
+
retval = json_query(address, port, timeout)
|
52
|
+
# 1.6
|
53
|
+
unless retval == Retval::SUCCESS || retval == Retval::CONNFAIL
|
54
|
+
retval = new_query(address, port, timeout)
|
55
|
+
end
|
56
|
+
# 1.4/1.5
|
57
|
+
unless retval == Retval::SUCCESS || retval == Retval::CONNFAIL
|
58
|
+
retval = legacy_query(address, port, timeout)
|
59
|
+
end
|
60
|
+
# 1.8b/1.3
|
61
|
+
unless retval == Retval::SUCCESS || retval == Retval::CONNFAIL
|
62
|
+
retval = beta_query(address, port, timeout)
|
63
|
+
end
|
64
|
+
|
65
|
+
@online = false unless retval == Retval::SUCCESS
|
66
|
+
end
|
67
|
+
|
68
|
+
# 1.8b/1.3
|
69
|
+
# 1.8 beta through 1.3 servers communicate as follows for a ping query:
|
70
|
+
# 1. Client sends \xFE (server list ping)
|
71
|
+
# 2. Server responds with:
|
72
|
+
# 2a. \xFF (kick packet)
|
73
|
+
# 2b. data length
|
74
|
+
# 2c. 3 fields delimited by \u00A7 (section symbol)
|
75
|
+
# The 3 fields, in order, are: message of the day, current players, and max players
|
76
|
+
def beta_query(address, port, timeout)
|
77
|
+
begin
|
78
|
+
data = nil
|
79
|
+
Timeout::timeout(timeout) do
|
80
|
+
start_time = Time.now
|
81
|
+
server = TCPSocket.new(address, port)
|
82
|
+
@latency = ((Time.now - start_time) * 1000).round
|
83
|
+
server.write("\xFE")
|
84
|
+
if server.read(1).unpack('C').first == 0xFF # kick packet (255)
|
85
|
+
len = server.read(2).unpack('n').first
|
86
|
+
data = server.read(len * 2).force_encoding('UTF-16BE').encode('UTF-8')
|
87
|
+
server.close
|
88
|
+
else
|
89
|
+
server.close
|
90
|
+
return Retval::UNKNOWN
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
if data == nil || data.empty?
|
95
|
+
return Retval::UNKNOWN
|
96
|
+
else
|
97
|
+
server_info = data.split("\u00A7") # section symbol
|
98
|
+
if server_info != nil && server_info.length >= NUM_FIELDS_BETA
|
99
|
+
@version = "1.8b/1.3" # since server does not return version, set it
|
100
|
+
@motd = server_info[0]
|
101
|
+
@current_players = server_info[1]
|
102
|
+
@max_players = server_info[2]
|
103
|
+
@online = true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
107
|
+
return Retval::CONNFAIL
|
108
|
+
rescue Timeout::Error
|
109
|
+
return Retval::TIMEOUT
|
110
|
+
rescue => exception
|
111
|
+
$stderr.puts exception
|
112
|
+
return Retval::UNKNOWN
|
113
|
+
end
|
114
|
+
return Retval::SUCCESS
|
115
|
+
end
|
33
116
|
|
34
|
-
|
117
|
+
# 1.4/1.5
|
118
|
+
# 1.4 and 1.5 servers communicate as follows for a ping query:
|
119
|
+
# 1. Client sends:
|
120
|
+
# 1a. \xFE (server list ping)
|
121
|
+
# 1b. \x01 (server list ping payload)
|
122
|
+
# 2. Server responds with:
|
123
|
+
# 2a. \xFF (kick packet)
|
124
|
+
# 2b. data length
|
125
|
+
# 2c. 6 fields delimited by \x00 (null)
|
126
|
+
# The 6 fields, in order, are: the section symbol and 1, protocol version,
|
127
|
+
# server version, message of the day, current players, and max players
|
128
|
+
# The protocol version corresponds with the server version and can be the
|
129
|
+
# same for different server versions.
|
130
|
+
def legacy_query(address, port, timeout)
|
35
131
|
begin
|
36
132
|
data = nil
|
37
133
|
Timeout::timeout(timeout) do
|
134
|
+
start_time = Time.now
|
38
135
|
server = TCPSocket.new(address, port)
|
136
|
+
@latency = ((Time.now - start_time) * 1000).round
|
39
137
|
server.write("\xFE\x01")
|
40
|
-
|
41
|
-
|
138
|
+
if server.read(1).unpack('C').first == 0xFF # kick packet (255)
|
139
|
+
len = server.read(2).unpack('n').first
|
140
|
+
data = server.read(len * 2).force_encoding('UTF-16BE').encode('UTF-8')
|
141
|
+
server.close
|
142
|
+
else
|
143
|
+
server.close
|
144
|
+
return Retval::UNKNOWN
|
145
|
+
end
|
42
146
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
147
|
+
|
148
|
+
if data == nil || data.empty?
|
149
|
+
return Retval::UNKNOWN
|
150
|
+
else
|
151
|
+
server_info = data.split("\x00") # null
|
152
|
+
if server_info != nil && server_info.length >= NUM_FIELDS
|
153
|
+
# server_info[0] contains the section symbol and 1
|
154
|
+
# server_info[1] contains the protocol version (51 for example)
|
155
|
+
@version = server_info[2]
|
156
|
+
@motd = server_info[3]
|
157
|
+
@current_players = server_info[4]
|
158
|
+
@max_players = server_info[5]
|
159
|
+
@online = true
|
160
|
+
else
|
161
|
+
return Retval::UNKNOWN
|
162
|
+
end
|
163
|
+
end
|
164
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
165
|
+
return Retval::CONNFAIL
|
166
|
+
rescue Timeout::Error
|
167
|
+
return Retval::TIMEOUT
|
168
|
+
rescue => exception
|
169
|
+
$stderr.puts exception
|
170
|
+
return Retval::UNKNOWN
|
47
171
|
end
|
172
|
+
return Retval::SUCCESS
|
173
|
+
end
|
48
174
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
175
|
+
# 1.6
|
176
|
+
# 1.6 servers communicate as follows for a ping query:
|
177
|
+
# 1. Client sends:
|
178
|
+
# 1a. \xFE (server list ping)
|
179
|
+
# 1b. \x01 (server list ping payload)
|
180
|
+
# 1c. \xFA (plugin message)
|
181
|
+
# 1d. \x00\x0B (11 which is the length of "MC|PingHost")
|
182
|
+
# 1e. "MC|PingHost" encoded as a UTF-16BE string
|
183
|
+
# 1f. length of remaining data as a short: remote address (encoded as UTF-16BE) + 7
|
184
|
+
# 1g. arbitrary 1.6 protocol version (\x4E for example for 78)
|
185
|
+
# 1h. length of remote address as a short
|
186
|
+
# 1i. remote address encoded as a UTF-16BE string
|
187
|
+
# 1j. remote port as an int
|
188
|
+
# 2. Server responds with:
|
189
|
+
# 2a. \xFF (kick packet)
|
190
|
+
# 2b. data length
|
191
|
+
# 2c. 6 fields delimited by \x00 (null)
|
192
|
+
# The 6 fields, in order, are: the section symbol and 1, protocol version,
|
193
|
+
# server version, message of the day, current players, and max players
|
194
|
+
# The protocol version corresponds with the server version and can be the
|
195
|
+
# same for different server versions.
|
196
|
+
def new_query(address, port, timeout)
|
197
|
+
begin
|
198
|
+
data = nil
|
199
|
+
Timeout::timeout(DEFAULT_TIMEOUT) do
|
200
|
+
start_time = Time.now
|
201
|
+
server = TCPSocket.new(address, port)
|
202
|
+
@latency = ((Time.now - start_time) * 1000).round
|
203
|
+
server.write("\xFE\x01\xFA")
|
204
|
+
server.write("\x00\x0B") # 11 (length of "MC|PingHost")
|
205
|
+
server.write('MC|PingHost'.encode('UTF-16BE').force_encoding('ASCII-8BIT'))
|
206
|
+
server.write([7 + 2 * address.length].pack('n'))
|
207
|
+
server.write("\x4E") # 78 (protocol version of 1.6.4)
|
208
|
+
server.write([address.length].pack('n'))
|
209
|
+
server.write(address.encode('UTF-16BE').force_encoding('ASCII-8BIT'))
|
210
|
+
server.write([port].pack('N'))
|
211
|
+
if server.read(1).unpack('C').first == 0xFF # kick packet (255)
|
212
|
+
len = server.read(2).unpack('n').first
|
213
|
+
data = server.read(len * 2).force_encoding('UTF-16BE').encode('UTF-8')
|
214
|
+
server.close
|
215
|
+
else
|
216
|
+
server.close
|
217
|
+
return Retval::UNKNOWN
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
if data == nil || data.empty?
|
222
|
+
return Retval::UNKNOWN
|
60
223
|
else
|
61
|
-
|
224
|
+
server_info = data.split("\x00") # null
|
225
|
+
if server_info != nil && server_info.length >= NUM_FIELDS
|
226
|
+
# server_info[0] contains the section symbol and 1
|
227
|
+
# server_info[1] contains the protocol version (78 for example)
|
228
|
+
@version = server_info[2]
|
229
|
+
@motd = server_info[3]
|
230
|
+
@current_players = server_info[4]
|
231
|
+
@max_players = server_info[5]
|
232
|
+
@online = true
|
233
|
+
else
|
234
|
+
return Retval::UNKNOWN
|
235
|
+
end
|
62
236
|
end
|
237
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
238
|
+
return Retval::CONNFAIL
|
239
|
+
rescue Timeout::Error
|
240
|
+
return Retval::TIMEOUT
|
241
|
+
rescue => exception
|
242
|
+
$stderr.puts exception
|
243
|
+
return Retval::UNKNOWN
|
63
244
|
end
|
245
|
+
return Retval::SUCCESS
|
246
|
+
end
|
247
|
+
|
248
|
+
# 1.7
|
249
|
+
# 1.7 to current servers communicate as follows for a ping query:
|
250
|
+
# 1. Client sends:
|
251
|
+
# 1a. \x00 (handshake packet containing the fields specified below)
|
252
|
+
# 1b. \x00 (request)
|
253
|
+
# The handshake packet contains the following fields respectively:
|
254
|
+
# 1. protocol version as a varint (\x00 suffices)
|
255
|
+
# 2. remote address as a string
|
256
|
+
# 3. remote port as an unsigned short
|
257
|
+
# 4. state as a varint (should be 1 for status)
|
258
|
+
# 2. Server responds with:
|
259
|
+
# 2a. \x00 (JSON response)
|
260
|
+
# An example JSON string contains:
|
261
|
+
# {'players': {'max': 20, 'online': 0},
|
262
|
+
# 'version': {'protocol': 404, 'name': '1.13.2'},
|
263
|
+
# 'description': {'text': 'A Minecraft Server'}}
|
264
|
+
def json_query(address, port, timeout)
|
265
|
+
return Retval::UNKNOWN # ToDo: Implement me!
|
64
266
|
end
|
65
267
|
|
66
|
-
attr_reader :address, :port, :online, :version, :motd, :current_players, :max_players
|
268
|
+
attr_reader :address, :port, :online, :version, :motd, :current_players, :max_players, :latency
|
67
269
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minestat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lloyd Dilley
|
@@ -9,17 +9,20 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2020-04-26 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description:
|
15
|
-
|
14
|
+
description: MineStat polls Minecraft server data such as version, motd, current players,
|
15
|
+
and max players using a variety of programming languages.
|
16
|
+
email:
|
17
|
+
- minecraft@frag.land
|
16
18
|
executables: []
|
17
19
|
extensions: []
|
18
20
|
extra_rdoc_files: []
|
19
21
|
files:
|
20
22
|
- lib/minestat.rb
|
21
|
-
homepage: https://github.com/
|
22
|
-
licenses:
|
23
|
+
homepage: https://github.com/FragLand/minestat
|
24
|
+
licenses:
|
25
|
+
- GPL-3.0
|
23
26
|
metadata: {}
|
24
27
|
post_install_message:
|
25
28
|
rdoc_options: []
|
@@ -37,7 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
37
40
|
version: '0'
|
38
41
|
requirements: []
|
39
42
|
rubyforge_project:
|
40
|
-
rubygems_version: 2.6.
|
43
|
+
rubygems_version: 2.7.6.2
|
41
44
|
signing_key:
|
42
45
|
specification_version: 4
|
43
46
|
summary: Minecraft server status checker
|