diff --git a/README.md b/README.md index 89a16bcfe1bb80c08dd84ed7ca1d1c2f99597e05..9be60179d53284e4092d628fb151a9a607ba3781 100644 --- a/README.md +++ b/README.md @@ -771,6 +771,16 @@ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string * **Documentation**: http://ipinfo.io/developers * **Terms of Service**: http://ipinfo.io/developers +#### IP-API.com (`:ipapi_com`) + +* **API key**: optional - see http://ip-api.com/docs/#usage_limits +* **Quota**: 150/minute - unlimited with api key +* **Region**: world +* **SSL support**: no (not without access key - see https://signup.ip-api.com/) +* **Languages**: English +* **Documentation**: http://ip-api.com/docs/ +* **Terms of Service**: https://signup.ip-api.com/terms + ### IP Address Local Database Services #### MaxMind Local (`:maxmind_local`) - EXPERIMENTAL diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb index d09bd3ac91ed9144a4787acff5f50ed9b6c1bd23..fdc4a0705b09020079320b1e25fb3ffea3ffb3db 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookup.rb @@ -64,7 +64,8 @@ module Geocoder :telize, :pointpin, :maxmind_geoip2, - :ipinfo_io + :ipinfo_io, + :ipapi_com ] end diff --git a/lib/geocoder/lookups/ipapi_com.rb b/lib/geocoder/lookups/ipapi_com.rb new file mode 100644 index 0000000000000000000000000000000000000000..df6f098b4182da3b7b7ac5b5235ba9fe19781b74 --- /dev/null +++ b/lib/geocoder/lookups/ipapi_com.rb @@ -0,0 +1,64 @@ +require 'geocoder/lookups/base' +require 'geocoder/results/ipapi_com' + +module Geocoder::Lookup + class IpapiCom < Base + + def name + "ip-api.com" + end + + def query_url(query) + url_ = "#{protocol}://ip-api.com/json/#{query.sanitized_text}" + + if (params = url_query_string(query)) && !params.empty? + url_ + "?" + params + else + url_ + end + end + + def supported_protocols + if configuration.api_key + [:http, :https] + else + [:http] + end + end + + + private + + def results(query) + return [reserved_result(query.text)] if query.loopback_ip_address? + + (doc = fetch_data(query)) ? [doc] : [] + end + + def reserved_result(query) + { + "message" => "reserved range", + "query" => query, + "status" => fail, + "ip" => query, + "city" => "", + "region_code" => "", + "region_name" => "", + "metrocode" => "", + "zipcode" => "", + "latitude" => "0", + "longitude" => "0", + "country_name" => "Reserved", + "country_code" => "RD" + } + end + + def query_url_params(query) + params = {} + params.merge!(fields: configuration[:fields]) if configuration.has_key?(:fields) + params.merge!(key: configuration.api_key) if configuration.api_key + params.merge(super) + end + + end +end diff --git a/lib/geocoder/results/ipapi_com.rb b/lib/geocoder/results/ipapi_com.rb new file mode 100644 index 0000000000000000000000000000000000000000..0ac3264868646879ca937306a2db67651472e764 --- /dev/null +++ b/lib/geocoder/results/ipapi_com.rb @@ -0,0 +1,53 @@ +require 'geocoder/results/base' + +module Geocoder::Result + class IpapiCom < Base + + def latitude + lat + end + + def longitude + lon + end + + def coordinates + [lat, lon] + end + + def address + "#{city}, #{state_code} #{postal_code}, #{country}".sub(/^[ ,]*/, "") + end + + def state + region_name + end + + def state_code + region + end + + def postal_code + zip + end + + def country_code + @data['countryCode'] + end + + def region_name + @data['regionName'] + end + + def self.response_attributes + %w[country region city zip timezone isp org as reverse query status message mobile proxy lat lon] + end + + response_attributes.each do |attribute| + define_method attribute do + @data[attribute] + end + end + + end +end diff --git a/test/fixtures/ipapi_com_74_200_247_59 b/test/fixtures/ipapi_com_74_200_247_59 new file mode 100644 index 0000000000000000000000000000000000000000..5304304721bf7fd917a3b54072a77182b2f5634f --- /dev/null +++ b/test/fixtures/ipapi_com_74_200_247_59 @@ -0,0 +1,19 @@ +{ + "as": "AS22576 DataPipe, Inc.", + "city": "Jersey City", + "country": "United States", + "countryCode": "US", + "isp": "DataPipe", + "lat": 40.7209, + "lon": -74.0468, + "mobile": false, + "org": "DataPipe", + "proxy": false, + "query": "74.200.247.59", + "region": "NJ", + "regionName": "New Jersey", + "reverse": "", + "status": "success", + "timezone": "America/New_York", + "zip": "07302" +} diff --git a/test/fixtures/ipapi_com_no_results b/test/fixtures/ipapi_com_no_results new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/test_helper.rb b/test/test_helper.rb index 4a9c4d62c1fe549b51ef1865312d46cd02ffe939..8ad22fc798c4f541acfeab13b8540fe72fba2bff 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -336,6 +336,14 @@ module Geocoder end end + require 'geocoder/lookups/ipapi_com' + class IpapiCom + private + def default_fixture_filename + "ipapi_com_74_200_247_59" + end + end + end end diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb index 496bb5bfdcf5edc352552b5f4755353495f98325..5a91ad573596da1fbf2a8acda5b3772ff882a721 100644 --- a/test/unit/lookup_test.rb +++ b/test/unit/lookup_test.rb @@ -24,7 +24,7 @@ class LookupTest < GeocoderTestCase def test_query_url_contains_values_in_params_hash Geocoder::Lookup.all_services_except_test.each do |l| - next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipinfo_io].include? l # does not use query string + next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipinfo_io, :ipapi_com].include? l # does not use query string set_api_key!(l) url = Geocoder::Lookup.get(l).query_url(Geocoder::Query.new( "test", :params => {:one_in_the_hand => "two in the bush"} diff --git a/test/unit/lookups/ipapi_com_test.rb b/test/unit/lookups/ipapi_com_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..6cb07cb9545cd98ff4433872837030351ac9a311 --- /dev/null +++ b/test/unit/lookups/ipapi_com_test.rb @@ -0,0 +1,73 @@ +# encoding: utf-8 +require 'test_helper' + +class IpapiComTest < GeocoderTestCase + + def setup + Geocoder::Configuration.instance.data.clear + Geocoder::Configuration.set_defaults + Geocoder.configure(ip_lookup: :ipapi_com) + end + + def test_result_on_ip_address_search + result = Geocoder.search("74.200.247.59").first + assert result.is_a?(Geocoder::Result::IpapiCom) + end + + def test_result_components + result = Geocoder.search("74.200.247.59").first + assert_equal "Jersey City, NJ 07302, United States", result.address + end + + def test_all_api_fields + result = Geocoder.search("74.200.247.59").first + assert_equal "United States", result.country + assert_equal "US", result.country_code + assert_equal "NJ", result.region + assert_equal "New Jersey", result.region_name + assert_equal "Jersey City", result.city + assert_equal "07302", result.zip + assert_equal 40.7209, result.lat + assert_equal -74.0468, result.lon + assert_equal "America/New_York", result.timezone + assert_equal "DataPipe", result.isp + assert_equal "DataPipe", result.org + assert_equal "AS22576 DataPipe, Inc.", result.as + assert_equal "", result.reverse + assert_equal false, result.mobile + assert_equal false, result.proxy + assert_equal "74.200.247.59", result.query + assert_equal "success", result.status + assert_equal nil, result.message + end + + def test_api_key + Geocoder.configure(:api_key => "MY_KEY") + g = Geocoder::Lookup::IpapiCom.new + assert_match "key=MY_KEY", g.query_url(Geocoder::Query.new("74.200.247.59")) + end + + def test_url_with_api_key_and_fields + Geocoder.configure(:api_key => "MY_KEY", :ipapi_com => {:fields => "lat,lon,xyz"}) + g = Geocoder::Lookup::IpapiCom.new + assert_equal "http://ip-api.com/json/74.200.247.59?fields=lat%2Clon%2Cxyz&key=MY_KEY", g.query_url(Geocoder::Query.new("74.200.247.59")) + end + + def test_url_with_fields + Geocoder.configure(:ipapi_com => {:fields => "lat,lon"}) + g = Geocoder::Lookup::IpapiCom.new + assert_equal "http://ip-api.com/json/74.200.247.59?fields=lat%2Clon", g.query_url(Geocoder::Query.new("74.200.247.59")) + end + + def test_url_without_fields + g = Geocoder::Lookup::IpapiCom.new + assert_equal "http://ip-api.com/json/74.200.247.59", g.query_url(Geocoder::Query.new("74.200.247.59")) + end + + def test_search_with_params + g = Geocoder::Lookup::IpapiCom.new + q = Geocoder::Query.new("74.200.247.59", :params => {:fields => 'lat,zip'}) + assert_equal "http://ip-api.com/json/74.200.247.59?fields=lat%2Czip", g.query_url(q) + end + +end