diff --git a/README.md b/README.md index 5508fa5..2c4539b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # T2ServerQuery -**TODO: Add description** +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 -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `t2_server_query` to your list of dependencies in `mix.exs`: +The package can be installed by adding `t2_server_query` to your list of dependencies in `mix.exs`: ```elixir def deps do @@ -15,7 +18,33 @@ def deps do end ``` -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at [https://hexdocs.pm/t2_server_query](https://hexdocs.pm/t2_server_query). + +## Usage + +```elixir +# T2ServerQuery.query("35.239.88.241", port // 28_000, timeout // 3_500) + 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. playt2.com/discord", + server_name: "Discord PUB", + server_status: :online, + team_count: 1, + teams: [%{name: "Storm", score: 0}] + }} +``` + +## Docs +The docs can be found at [https://hexdocs.pm/t2_server_query](https://hexdocs.pm/t2_server_query). + +Documentation has been generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). diff --git a/lib/t2_server_query.ex b/lib/t2_server_query.ex index af61349..096f0ac 100644 --- a/lib/t2_server_query.ex +++ b/lib/t2_server_query.ex @@ -1,12 +1,119 @@ defmodule T2ServerQuery do @moduledoc """ - Documentation for `T2ServerQuery`. + + 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.0"} + ] + end + + ## Usage + # T2ServerQuery.query("35.239.88.241", port // 28_000, timeout // 3_500) + T2ServerQuery.query("35.239.88.241") + + --- + """ require Logger - # Just a simple debug logging util + 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. playt2.com/discord", + 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: [] + }} + + """ + def query(server_ip, port \\ 28_000, timeout \\ 3_500) do + Logger.info "query: #{server_ip}" + + {: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 + + + 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 diff --git a/lib/t2_server_query/packet_parser.ex b/lib/t2_server_query/packet_parser.ex index e9ae05a..37a0cf0 100644 --- a/lib/t2_server_query/packet_parser.ex +++ b/lib/t2_server_query/packet_parser.ex @@ -1,11 +1,58 @@ defmodule T2ServerQuery.PacketParser do @moduledoc """ - Documentation for `T2ServerQuery.PacketParser`. + This module does the heavy lifting with parsing a Tribes 2 query response packet. + + ## UDP Packet Anatomy + + ### Info Packet + << + _header :: size(192), + server_name :: bitstring + >> + + ### Status Packet + << + _header :: size(48), + + game_type_length :: little-integer, + game_type :: binary-size(game_type_length), + mission_type_length :: little-integer, + mission_type :: binary-size(mission_type_length), + map_name_length :: little-integer, + map_name :: binary-size(map_name_length), + + _skip_a :: size(8), + + player_count :: little-integer, + max_player_count :: little-integer, + bot_count :: little-integer, + + _skip_b :: size(16), + + server_description_length :: little-integer, + server_description :: binary-size(server_description_length), + + _skip_c :: size(16), + + team_count :: binary-size(1), + + rest :: bitstring + >> + + Notice the `_skip_(a|b|c)` mappings. I havn't quite figured out what they refer to yet but they don't seem that important. They likely relate to a few server flags like `tournament_mode`, `cpu_speed`, `is_linux`. + + Refer to `T2ServerQuery.QueryResult` for what a typical struct would look like. + + """ alias T2ServerQuery.QueryResult + @doc """ + This function expects both an `info` and `status` packet to be passed in that is in a `Base.encode16` format. + Normally you wouldn't need to run this function manually since it's called in a pipeline from the main `T2ServerQuery.query` + """ def init({:error, host}, _) do results = %QueryResult{} diff --git a/lib/t2_server_query/query_result.ex b/lib/t2_server_query/query_result.ex index 6beab28..e61dc2c 100644 --- a/lib/t2_server_query/query_result.ex +++ b/lib/t2_server_query/query_result.ex @@ -1,51 +1,51 @@ defmodule T2ServerQuery.QueryResult do @moduledoc """ - Shape of the server query result struct. + ## Struct Shape - %T2ServerQuery.QueryResult{ - server_status: :online, - bot_count: 30, - game_type: "Classic", - map_name: "Cold as Ice [b]", - max_player_count: 64, - mission_type: "Capture the Flag", - player_count: 29, - players: [ - %{player: "Rooster128", score: "0", team: "Storm"}, - %{player: "sneakygnome", score: "0", team: "Inferno"}, - %{player: "Waldred ", score: "0", team: "Inferno"}, - %{player: "HDPTetchy ", score: "0", team: "Storm"}, - %{player: "0wnj0o", score: "0", team: "Inferno"}, - %{player: "idjit ", score: "0", team: "Storm"}, - %{player: "JesusChrist ", score: "0", team: "Storm"}, - %{player: "Sofaking--bakeD ", score: "0", team: "Inferno"}, - %{player: "saKe ", score: "0", team: "Inferno"}, - %{player: "ZurkinWood497", score: "0", team: "Storm"}, - %{player: "TerryTC ", score: "0", team: "Inferno"}, - %{player: "WankBullet ", score: "0", team: "Storm"}, - %{player: "CyClones", score: "0", team: "Inferno"}, - %{player: "huntergirl10", score: "0", team: "Storm"}, - %{player: "ChocoTaco", score: "0", team: "Inferno"}, - %{player: "Dirk", score: "0", team: "Storm"}, - %{player: "Krell", score: "0", team: "Storm"}, - %{player: "high5slayer", score: "0", team: "Inferno"}, - %{player: "Red Fraction ", score: "0", team: "Inferno"}, - %{player: "-MaLice--", score: "0", team: "Storm"}, - %{player: "wiltedflower ", score: "0", team: "Inferno"}, - %{player: "Glarm ", score: "0", team: "Storm"}, - %{player: "AlphaSentinel", score: "0", team: "Inferno"}, - %{player: "The-Punisher ", score: "0", team: "Storm"}, - %{player: "2SmOkeD", score: "0", team: "Inferno"}, - %{player: "iPrecision", score: "0", team: "Storm"}, - %{player: "Halo 2 ", score: "0", team: "Storm"}, - %{player: "Sami-FIN ", score: "0", team: "Inferno"}, - %{player: "rileygarbels", score: "0", team: "Storm"} - ], - server_description: "This server is using bots that are adapted to playing Classic. http://tribes2bots.byethost4.com/forum/index.php?topic=57.msg234", - server_name: "Classic Bots Server", - team_count: 2, - teams: [%{name: "Storm", score: "0"}, %{name: "Inferno", score: "0"}] - } + %T2ServerQuery.QueryResult{ + server_status: :online, + bot_count: 30, + game_type: "Classic", + map_name: "Cold as Ice [b]", + max_player_count: 64, + mission_type: "Capture the Flag", + player_count: 29, + players: [ + %{player: "Rooster128", score: "0", team: "Storm"}, + %{player: "sneakygnome", score: "0", team: "Inferno"}, + %{player: "Waldred ", score: "0", team: "Inferno"}, + %{player: "HDPTetchy ", score: "0", team: "Storm"}, + %{player: "0wnj0o", score: "0", team: "Inferno"}, + %{player: "idjit ", score: "0", team: "Storm"}, + %{player: "JesusChrist ", score: "0", team: "Storm"}, + %{player: "Sofaking--bakeD ", score: "0", team: "Inferno"}, + %{player: "saKe ", score: "0", team: "Inferno"}, + %{player: "ZurkinWood497", score: "0", team: "Storm"}, + %{player: "TerryTC ", score: "0", team: "Inferno"}, + %{player: "WankBullet ", score: "0", team: "Storm"}, + %{player: "CyClones", score: "0", team: "Inferno"}, + %{player: "huntergirl10", score: "0", team: "Storm"}, + %{player: "ChocoTaco", score: "0", team: "Inferno"}, + %{player: "Dirk", score: "0", team: "Storm"}, + %{player: "Krell", score: "0", team: "Storm"}, + %{player: "high5slayer", score: "0", team: "Inferno"}, + %{player: "Red Fraction ", score: "0", team: "Inferno"}, + %{player: "-MaLice--", score: "0", team: "Storm"}, + %{player: "wiltedflower ", score: "0", team: "Inferno"}, + %{player: "Glarm ", score: "0", team: "Storm"}, + %{player: "AlphaSentinel", score: "0", team: "Inferno"}, + %{player: "The-Punisher ", score: "0", team: "Storm"}, + %{player: "2SmOkeD", score: "0", team: "Inferno"}, + %{player: "iPrecision", score: "0", team: "Storm"}, + %{player: "Halo 2 ", score: "0", team: "Storm"}, + %{player: "Sami-FIN ", score: "0", team: "Inferno"}, + %{player: "rileygarbels", score: "0", team: "Storm"} + ], + server_description: "This server is using bots that are adapted to playing Classic. http://tribes2bots.byethost4.com/forum/index.php?topic=57.msg234", + server_name: "Classic Bots Server", + team_count: 2, + teams: [%{name: "Storm", score: "0"}, %{name: "Inferno", score: "0"}] + } """ defstruct [ diff --git a/lib/t2_server_query/udp_server.ex b/lib/t2_server_query/udp_server.ex deleted file mode 100644 index 27f98c0..0000000 --- a/lib/t2_server_query/udp_server.ex +++ /dev/null @@ -1,94 +0,0 @@ -defmodule T2ServerQuery.UdpServer do - @moduledoc """ - Documentation for `UdpServer`. - """ - require Logger - - alias T2ServerQuery.PacketParser - - @doc """ - Perform a server query. - Results should be in the form of a tuple - - {:ok, %T2ServerQuery.QueryResult{}} - - {:error, %T2ServerQuery.QueryResult{}} - - Querying a Tribes 2 server actually requires sending 2 different packets to the server where the first byte is denoting what we're asking for in response. 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. - - - ## Examples - - iex> T2ServerQuery.UdpServer.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. playt2.com/discord", - server_name: "Discord PUB", - server_status: :online, - team_count: 1, - teams: [%{name: "Storm", score: 0}] - }} - - iex> T2ServerQuery.UdpServer.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: [] - }} - - """ - def query(server_ip, port \\ 28_000, timeout \\ 3_500) do - Logger.info "query: #{server_ip}" - - {: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 - - - 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 - -end diff --git a/mix.exs b/mix.exs index e7b30a8..fae6ba5 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,17 @@ defmodule T2ServerQuery.MixProject do version: "0.1.0", elixir: "~> 1.12", start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + + # Docs + name: "T2ServerQuery", + description: "Query any Tribes 2 server and retrieve the current map, players, team scores and more!", + source_url: "https://github.com/amineo/t2_server_query_elixir", + homepage_url: "https://github.com/amineo/t2_server_query_elixir", + docs: [ + main: "T2ServerQuery", + extras: ["README.md"] + ] ] end @@ -21,7 +31,8 @@ defmodule T2ServerQuery.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:credo, "~> 1.5"} + {:credo, "~> 1.5"}, + {:ex_doc, "~> 0.24", only: :dev, runtime: false} ] end end diff --git a/mix.lock b/mix.lock index bf1731d..3681bd1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,12 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.16", "607709303e1d4e3e02f1444df0c821529af1c03b8578dfc81bb9cf64553d02b9", [:mix], [], "hexpm", "69fcf696168f5a274dd012e3e305027010658b2d1630cef68421d6baaeaccead"}, + "ex_doc": {:hex, :ex_doc, "0.25.3", "3edf6a0d70a39d2eafde030b8895501b1c93692effcbd21347296c18e47618ce", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "9ebebc2169ec732a38e9e779fd0418c9189b3ca93f4a676c961be6c1527913f5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, } diff --git a/test/t2_server_query_test.exs b/test/t2_server_query_test.exs index 83b0a0f..985cc88 100644 --- a/test/t2_server_query_test.exs +++ b/test/t2_server_query_test.exs @@ -1,8 +1,7 @@ defmodule T2ServerQueryTest do use ExUnit.Case, async: true - alias T2ServerQuery.UdpServer - doctest T2ServerQuery + # doctest T2ServerQuery test "Gracefully handle timeouts and unreachable servers" do @@ -10,7 +9,7 @@ defmodule T2ServerQueryTest do timeout = 250 port = Enum.random(28_000..28_999) - {:error, result} = T2ServerQuery.UdpServer.query("127.0.0.1", port, timeout) + {:error, result} = T2ServerQuery.query("127.0.0.1", port, timeout) |> T2ServerQuery.log assert result.server_status == :offline @@ -21,9 +20,9 @@ defmodule T2ServerQueryTest do test "Live test a number of Tribes 2 servers" do tasks = [ - Task.async(T2ServerQuery.UdpServer, :query, ["35.239.88.241"]), - Task.async(T2ServerQuery.UdpServer, :query, ["97.99.172.12", 28_001]), - Task.async(T2ServerQuery.UdpServer, :query, ["67.222.138.13"]) + Task.async(T2ServerQuery, :query, ["35.239.88.241"]), + Task.async(T2ServerQuery, :query, ["97.99.172.12", 28_001]), + Task.async(T2ServerQuery, :query, ["67.222.138.13"]) ] server_list = Task.yield_many(tasks) @@ -50,28 +49,28 @@ end -#qry_test = T2ServerQuery.UdpServer.query("127.0.0.1") +#qry_test = T2ServerQuery.query("127.0.0.1") #IO.inspect qry_test -#qry_test2 = T2ServerQuery.UdpServer.query("35.239.88.241") +#qry_test2 = T2ServerQuery.query("35.239.88.241") #IO.inspect qry_test2 # tasks = [ -# Task.async(T2ServerQuery.UdpServer, :query, ["127.0.0.1"]), -# Task.async(T2ServerQuery.UdpServer, :query, ["35.239.88.241"]), -# Task.async(T2ServerQuery.UdpServer, :query, ["97.99.172.12", 28001]), -# Task.async(T2ServerQuery.UdpServer, :query, ["67.222.138.13"]), -# Task.async(T2ServerQuery.UdpServer, :query, ["91.55.51.94"]), +# Task.async(T2ServerQuery, :query, ["127.0.0.1"]), +# Task.async(T2ServerQuery, :query, ["35.239.88.241"]), +# Task.async(T2ServerQuery, :query, ["97.99.172.12", 28001]), +# Task.async(T2ServerQuery, :query, ["67.222.138.13"]), +# Task.async(T2ServerQuery, :query, ["91.55.51.94"]), # ] # IO.inspect Task.yield_many(tasks) -# task0 = Task.async(T2ServerQuery.UdpServer, :query, ["127.0.0.1"]) -# task1 = Task.async(T2ServerQuery.UdpServer, :query, ["35.239.88.241"]) -# task2 = Task.async(T2ServerQuery.UdpServer, :query, ["97.99.172.12", 28001]) -# task3 = Task.async(T2ServerQuery.UdpServer, :query, ["67.222.138.13"]) -# task4 = Task.async(T2ServerQuery.UdpServer, :query, ["91.55.51.94"]) +# task0 = Task.async(T2ServerQuery, :query, ["127.0.0.1"]) +# task1 = Task.async(T2ServerQuery, :query, ["35.239.88.241"]) +# task2 = Task.async(T2ServerQuery, :query, ["97.99.172.12", 28001]) +# task3 = Task.async(T2ServerQuery, :query, ["67.222.138.13"]) +# task4 = Task.async(T2ServerQuery, :query, ["91.55.51.94"]) # # res4 = Task.await(task4) # # IO.inspect res4.server_name @@ -87,7 +86,7 @@ end # IO.inspect res3.server_name # IO.inspect res4.server_name -# T2ServerQuery.UdpServer.query("35.239.88.241") -# T2ServerQuery.UdpServer.query("97.99.172.12", 28001) -# T2ServerQuery.UdpServer.query("67.222.138.13") -# T2ServerQuery.UdpServer.query("91.55.51.94") +# T2ServerQuery.query("35.239.88.241") +# T2ServerQuery.query("97.99.172.12", 28001) +# T2ServerQuery.query("67.222.138.13") +# T2ServerQuery.query("91.55.51.94")