mirror of
https://github.com/amineo/t2_server_query_elixir.git
synced 2026-01-20 02:24:46 +00:00
142 lines
4.1 KiB
Elixir
142 lines
4.1 KiB
Elixir
defmodule T2ServerQuery do
|
|
@moduledoc """
|
|
|
|
Querying a Tribes 2 server actually requires sending 2 different packets to the server where the first byte is denoting the type of information we're asking for. The first is called the `info` packet which doesnt contain much more then the server name. The second is called the `status` packet which contains all the meat and potatoes.
|
|
|
|
The `T2ServerQuery.query/3` function makes requests for both `info` and `status` and combines them into a single response for easy consumption.
|
|
|
|
|
|
## Installation
|
|
def deps do
|
|
[
|
|
{:t2_server_query, "~> 0.1.2"}
|
|
]
|
|
end
|
|
|
|
## Usage
|
|
# T2ServerQuery.query("35.239.88.241", port // 28_000, timeout // 3_500)
|
|
T2ServerQuery.query("35.239.88.241")
|
|
|
|
---
|
|
|
|
"""
|
|
|
|
require Logger
|
|
|
|
alias T2ServerQuery.PacketParser
|
|
|
|
@doc """
|
|
Perform a server query. **Results should be in the form of a tuple with either `:ok` or `:error`**
|
|
|
|
{:ok, %T2ServerQuery.QueryResult{...} }
|
|
|
|
{:error, %T2ServerQuery.QueryResult{...} }
|
|
|
|
|
|
## Examples
|
|
|
|
iex> T2ServerQuery.query("35.239.88.241")
|
|
{:ok,
|
|
%T2ServerQuery.QueryResult{
|
|
bot_count: 0,
|
|
game_type: "Classic",
|
|
map_name: "Canker",
|
|
max_player_count: 64,
|
|
mission_type: "LakRabbit",
|
|
player_count: 0,
|
|
players: [%{}],
|
|
server_description: "Celebrating 20 Years of Tribes2! More information in Discord. <a:playt2.com/discord>playt2.com/discord</a>",
|
|
server_name: "Discord PUB",
|
|
server_status: :online,
|
|
team_count: 1,
|
|
teams: [%{name: "Storm", score: 0}]
|
|
}}
|
|
|
|
iex> T2ServerQuery.query("127.0.0.1")
|
|
{:error,
|
|
%T2ServerQuery.QueryResult{
|
|
bot_count: 0,
|
|
game_type: "",
|
|
map_name: "",
|
|
max_player_count: 0,
|
|
mission_type: "",
|
|
player_count: 0,
|
|
players: [],
|
|
server_description: "Host unreachable, timed out.",
|
|
server_name: "127.0.0.1:28000",
|
|
server_status: :offline,
|
|
team_count: 0,
|
|
teams: []
|
|
}}
|
|
|
|
"""
|
|
@spec query(String.t(), integer(), integer()) :: {atom(), %T2ServerQuery.QueryResult{}}
|
|
def query(server_ip, port \\ 28_000, timeout \\ 3_500) do
|
|
Logger.info "query: #{server_ip}"
|
|
case is_valid_ip?(server_ip) do
|
|
true -> handle_query(server_ip, port, timeout)
|
|
false -> PacketParser.init({:error, "#{server_ip} - Invalid IP" }, nil)
|
|
end
|
|
end
|
|
|
|
@spec handle_query(String.t(), integer(), integer()) :: {atom(), %T2ServerQuery.QueryResult{}}
|
|
defp handle_query(server_ip, port, timeout) do
|
|
{:ok, socket} = :gen_udp.open(0, [:binary, {:active, false}])
|
|
|
|
# Convert a string ip from "127.0.0.1" into {127, 0, 0, 1}
|
|
{:ok, s_ip} = server_ip
|
|
|> to_charlist()
|
|
|> :inet.parse_address()
|
|
|
|
|
|
qry_info_packet = <<14, 2, 1, 2, 3, 4>>
|
|
qry_status_packet = <<18, 2, 1, 2, 3, 4>>
|
|
|
|
# Requst info packet
|
|
:gen_udp.send(socket, s_ip, port, qry_info_packet)
|
|
hex_info_packet = :gen_udp.recv(socket, 0, timeout)
|
|
|> handle_udp_response(server_ip, port)
|
|
|
|
# Request status packet
|
|
:gen_udp.send(socket, s_ip, port, qry_status_packet)
|
|
hex_status_packet = :gen_udp.recv(socket, 0, timeout)
|
|
|> handle_udp_response(server_ip, port)
|
|
|
|
# Combine and parse results
|
|
PacketParser.init(hex_info_packet, hex_status_packet)
|
|
end
|
|
|
|
@spec is_valid_ip?(any()) :: boolean()
|
|
defp is_valid_ip?(nil), do: false
|
|
defp is_valid_ip?(server_ip) do
|
|
case Regex.match?(~r/^([1-2]?[0-9]{1,2}\.){3}([1-2]?[0-9]{1,2})$/, server_ip) do
|
|
false -> false
|
|
true -> true
|
|
end
|
|
end
|
|
|
|
|
|
@spec handle_udp_response(tuple(), String.t(), integer()) :: tuple() | String.t()
|
|
defp handle_udp_response({:ok, {_ip, port, packet}}, _server_ip, port) do
|
|
packet
|
|
|> Base.encode16
|
|
end
|
|
|
|
defp handle_udp_response({:error, :timeout}, server_ip, port) do
|
|
Logger.error "TIMEOUT --> #{server_ip}:#{port}"
|
|
{:error, "#{server_ip}:#{port}"}
|
|
end
|
|
|
|
|
|
|
|
@doc false
|
|
def log(thing_to_log) do
|
|
# Just a simple debug logging util
|
|
Logger.info(inspect thing_to_log)
|
|
IO.puts "\n____________________________________________\n"
|
|
thing_to_log
|
|
end
|
|
|
|
|
|
end
|