Skip to content

Instantly share code, notes, and snippets.

@wedtm
Created February 29, 2012 11:04
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save wedtm/1940035 to your computer and use it in GitHub Desktop.
Save wedtm/1940035 to your computer and use it in GitHub Desktop.
MCQuery - Minecraft Query Implementation in Ruby
require 'socket'
require 'timeout'
class MCQuery
MAGIC_PREFIX = "\xFE\xFD"
PACKET_TYPE_CHALLENGE = "\x09"
PACKET_TYPE_QUERY = "\x00"
ID = "\x00\x00\x00\x00"
DEFAULTS = {
host: "localhost",
port: 25565,
}
def initialize(opts = {})
@opts = DEFAULTS.merge! opts
end
def start
@socket = UDPSocket.new
@socket.connect(@opts[:host], @opts[:port])
@socket.send(MAGIC_PREFIX + PACKET_TYPE_CHALLENGE + ID, 0)
t = @socket.recvfrom(1460)[0]
key = t[5...-1].to_i
@key = Array(key).pack('N')
end
def quick
begin
timeout(5) do
begin
s = TCPSocket.open(@opts[:host], @opts[:port])
rescue Exception => ex
return false, ex
end
s.write("\xFE")
data = s.read()
data = data.gsub("\x00".force_encoding("ASCII-8BIT"), "").gsub("\x1A".force_encoding("ASCII-8BIT"), "").gsub("\xFF".force_encoding("ASCII-8BIT"), "")
results = data.split("\xA7".force_encoding("ASCII-8BIT"))
info = {}
info[:motd] = results[0]
info[:players] = results[1]
info[:max] = results[2]
info[:timestamp] = Time.now
return info
end
rescue Exception => ex
return false, ex
end
end
def simple
begin
timeout(5) do
start
query = @socket.send(MAGIC_PREFIX + PACKET_TYPE_QUERY + ID + @key, 0)
data = @socket.recvfrom(1460)[0]
buffer = data[5...-1]
val = {}
val[:motd], val[:gametype], val[:map], val[:numplayers], val[:maxplayers], buf = buffer.split("\x00", 6)
val[:timestamp] = Time.now
return val
end
rescue Exception => ex
return false, ex
end
end
def full
begin
timeout(10) do
start
query = @socket.send(MAGIC_PREFIX + PACKET_TYPE_QUERY + ID + @key + ID, 0)
data = @socket.recvfrom(1460)[0]
buffer = data[11...-1]
items, players = buffer.split("\x00\x00\x01player_\x00\x00")
if items[0...8] == 'hostname'
items = 'motd' + items[8...-1]
end
vals = {}
items = items.split("\x00")
items.each_with_index do |key, idx|
next unless idx % 2 == 0
vals[key] = items[idx + 1]
end
vals["motd"] = vals["hostname"]
vals.delete("hostname")
vals.delete("um") if vals["um"]
players = players[0..-2] if players
if players
vals[:players] = players.split("\x00")
end
puts vals
vals["raw_plugins"] = vals["plugins"]
parts = vals["raw_plugins"].split(":")
server = parts[0].strip() if parts[0]
plugins = []
if parts.size == 2
plugins = parts[1].split(";").map {|value| value.strip() }
end
vals["plugins"] = plugins
vals["server"] = server
vals["timestamp"] = Time.now
return vals.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
end
rescue StandardError => ex
return false, ex
end
end
end
Copy link

ghost commented Apr 28, 2013

Any ideas on why this would throw an Encoding::CompatibilityError most of the time (but still returns normally every once in awhile)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment