ruby-qstat
A wrapper gem for using QStat, a game server statistics tool, from Ruby. It launches the qstat command and returns the results — server lists fetched from master servers and status queries against individual game servers — as Ruby objects.
The requirements stated in the README are Ruby 1.9 and QStat 2.12.
Usage
gem install ruby-qstat
Examples from the README.
# マスターサーバーからサーバー一覧を取得
QStat.query_serverlist("hl2master.steampowered.com:27011", "stm", "left4dead2", maxping = 100)
# 個別サーバーの状態を取得
QStat.query("xxx.yyy.zzz.qqq", "a2s")
# 個別サーバーの状態を取得(サーバー情報のみ)
QStat.query_serverinfo("xxx.yyy.zzz.qqq", "a2s")
Internals
The library itself is a single file, lib/ruby-qstat.rb, and the API is provided as class methods on the QStat class (it cannot be instantiated). Launching the qstat command is centralized in a shared exec_cmd, and parsing splits into two paths depending on the output format.
- exec_cmd — launches qstat via
Open3.popen3and collects output with two threads: one reading stdout and one piping stderr to a Logger. If you pass a filter block, it is evaluated on every stdout line read, and once the condition holds the qstat process can be cut off withTERM. The qstat path is configurable viaQStat.qstat_path=and the logger viaQStat.logger= - XML path (
query_serverlist/qslist) — launches qstat with the-xmloption and parses the output with Nokogiri. AResponseobject holds both the XML and the DOM, and providesvalid?(whether the status of the leading server element is UP) andto_ror(hash conversion via ActiveSupport’sHash.from_xml) - Text path (
query/query_serverinfo) — parses the plain-text output produced with-P(player info) and-R(server rules) using regular expressions and builds aServerInfo. “no response” and “DOWN” replies are also detected via a regex on the header line - Result objects —
ServerInfo(address, server name, map, player count, ping, status, etc.),PlayerInfo(name, frags, play time; includes a parser that converts qstat time notation like “1h 2m 3s” to seconds), andRule(server rules such as protocol, gamedir, secure, version, game_tag).ServerInfo#playing_timereturns the play time of the longest-playing player - Game type inference — when no gametype could be obtained,
ServerInfo#suggest_game_typeinfers it from the game_tag server rule (the comma-separated tags after@), and falls back tounknownif that is missing too - Character encoding workarounds — qstat is always invoked with
-utf8, and additionallyString#force_convert_to(a monkey patch that round-trips through UTF-16BE, replacing invalid bytes with?) neutralizes broken byte sequences that creep into server names and the like
The flow of fetching a server list (query_serverlist). It is built to cut qstat off as soon as a server exceeding maxping appears, and the truncated XML is handed to Nokogiri as-is for parsing.
- XML whose tail was broken by the cutoff is still turned into a DOM thanks to Nokogiri’s lenient parsing, and server elements whose ping is at or above maxping are then removed from the DOM
- The leading server element in qstat’s XML output carries structural information, so it is removed (as noted in a code comment), and the rest is returned as an array of
ServerInfo - With
QStat.read_from_xmlyou can build an array ofServerInfofrom a saved XML file using the same procedure
My own gem vrowser (a server browser library for HalfLife2-family games) is built on top of this gem. It is also used by the server browser published on my site.