From d27a08a225759da18b22bd91575f58f1b33bb56e Mon Sep 17 00:00:00 2001 From: Frank Hunleth Date: Wed, 12 Jun 2024 11:53:30 -0400 Subject: [PATCH] geo: add geo-location utility --- lib/toolshed.ex | 2 +- lib_src/core/geo.ex | 94 ++++++++++++++++++++++++++++++++++++++ test/toolshed/geo_test.exs | 15 ++++++ test/toolshed_test.exs | 2 + 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 lib_src/core/geo.ex create mode 100644 test/toolshed/geo_test.exs diff --git a/lib/toolshed.ex b/lib/toolshed.ex index 592b2b2..fa6cb82 100644 --- a/lib/toolshed.ex +++ b/lib/toolshed.ex @@ -16,7 +16,7 @@ defmodule Toolshed do * `dmesg/0` - print kernel messages (Nerves-only) * `exit/0` - exit out of an IEx session * `fw_validate/0` - marks the current image as valid (check Nerves system if supported) - * `geo/0` - print out a rough physical location + * `geo/1` - print out a rough physical location * `grep/2` - print out lines that match a regular expression * `hex/1` - print a number as hex * `history/0` - print out the IEx shell history diff --git a/lib_src/core/geo.ex b/lib_src/core/geo.ex new file mode 100644 index 0000000..2b60a9a --- /dev/null +++ b/lib_src/core/geo.ex @@ -0,0 +1,94 @@ +defmodule Toolshed.Core.Geo do + @whenwhere_url "http://whenwhere.nerves-project.org/" + + @doc """ + Geo-locate this Elixir instance + + Options: + + * `:ifname` - Network interface to use (e.g., `"eth0"`) + * `:whenwhere_url` - URL for the whenwhere server to query. Defaults to http://whenwhere.nerves-project.org + """ + @spec geo(keyword()) :: :"do not show this result in output" + def geo(options \\ []) do + check_app(:inets) + check_app(:ssl) + + do_geo(options) |> IO.puts() + IEx.dont_display_result() + end + + defp do_geo(options) do + url = Keyword.get(options, :whenwhere_url, @whenwhere_url) + + request_headers = [ + {~c"user-agent", ~c"toolshed"}, + {~c"content-type", ~c"application/x-erlang-binary"} + ] + + case :httpc.request( + :get, + {url, request_headers}, + [ssl: [verify: :verify_none]], + socket_opts: socket_opts(options) + ) do + {:ok, {_status, _headers, body}} -> + body |> :erlang.list_to_binary() |> :erlang.binary_to_term() |> format_geo_output() + + {:error, reason} -> + error_message(reason) + end + rescue + e in MatchError -> error_message(e) + catch + :exit, reason -> error_message(reason) + end + + defp extract_ip(ip_port_string) do + case Regex.run(~r/^(.*):\d+$/, ip_port_string) do + [_, ip_address] -> ip_address + _ -> [] + end + end + + defp format_geo_output(result) do + now = NaiveDateTime.from_iso8601!(result["now"]) + + local_now = + with {:ok, time_zone} <- Map.fetch(result, "time_zone"), + {:ok, utc} <- DateTime.from_naive(now, "Etc/UTC"), + {:ok, dt} <- DateTime.shift_zone(utc, time_zone) do + dt + else + _ -> nil + end + + where = + [result["city"], result["country_region"], result["country"]] + |> Enum.filter(&Function.identity/1) + |> Enum.intersperse(", ") + + [ + "UTC Time : ", + NaiveDateTime.to_string(now), + "\n", + if(local_now, do: ["Local time: ", DateTime.to_string(local_now), "\n"], else: []), + ["Location : ", where, "\n"], + if(result["latitude"] && result["longitude"], + do: [ + "Map : https://www.openstreetmap.org/?mlat=", + result["latitude"], + "&mlon=", + result["longitude"], + "&zoom=12#map=12/", + result["latitude"], + "/", + result["longitude"], + "\n" + ], + else: [] + ), + if(result["address"], do: ["Public IP : ", extract_ip(result["address"])], else: []) + ] + end +end diff --git a/test/toolshed/geo_test.exs b/test/toolshed/geo_test.exs new file mode 100644 index 0000000..41ecb38 --- /dev/null +++ b/test/toolshed/geo_test.exs @@ -0,0 +1,15 @@ +defmodule Toolshed.GeoTest do + use ExUnit.Case + import ExUnit.CaptureIO + + test "geo/1 returns at least the time" do + # Everything else is optional unfortunately + assert capture_io(&Toolshed.geo/0) =~ "UTC Time : " + end + + test "geo/1 supports overriding the server" do + assert capture_io(fn -> + Toolshed.geo(whenwhere_url: "http://not_a_server.nerves-project.org") + end) =~ "Something went wrong when making an HTTP request" + end +end diff --git a/test/toolshed_test.exs b/test/toolshed_test.exs index a331629..1a2b478 100644 --- a/test/toolshed_test.exs +++ b/test/toolshed_test.exs @@ -6,6 +6,8 @@ defmodule ToolshedTest do cat: 1, cmd: 1, date: 0, + geo: 0, + geo: 1, grep: 2, hex: 1, history: 0,