Created
February 29, 2012 11:04
-
-
Save wedtm/1940035 to your computer and use it in GitHub Desktop.
MCQuery - Minecraft Query Implementation in Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Any ideas on why this would throw an
Encoding::CompatibilityError
most of the time (but still returns normally every once in awhile)?