diff --git a/README.md b/README.md index 68e8a79902f5a854b6082e3e69e0c85352b095be..d4db141b72abd678a530594fc81936f4ae14584e 100644 --- a/README.md +++ b/README.md @@ -856,6 +856,18 @@ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string * **Documentation**: https://db-ip.com/api/doc.php * **Terms of Service**: https://db-ip.com/tos.php +#### Ipdata.co (`:ipdata_co`) + +* **API key**: optional, see: https://ipdata.co/pricing.html +* **Quota**: 1500/day (up to 600k with paid API keys) +* **Region**: world +* **SSL support**: yes +* **Languages**: English +* **Documentation**: https://ipdata.co/docs.html +* **Terms of Service**: https://ipdata.co/terms.html +* **Limitations**: ? + + ### IP Address Local Database Services #### MaxMind Local (`:maxmind_local`) - EXPERIMENTAL diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb index 2d71041905c289b5990bfe0b3fa4b4b335a3d28a..b756f768f40e9fe94d6b80fa56505bf688a72c41 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookup.rb @@ -71,6 +71,7 @@ module Geocoder :maxmind_geoip2, :ipinfo_io, :ipapi_com, + :ipdata_co, :db_ip_com ] end diff --git a/lib/geocoder/lookups/ipdata_co.rb b/lib/geocoder/lookups/ipdata_co.rb new file mode 100644 index 0000000000000000000000000000000000000000..0e18fd8367daebaf6561330e16776a602d641aaf --- /dev/null +++ b/lib/geocoder/lookups/ipdata_co.rb @@ -0,0 +1,57 @@ +require 'geocoder/lookups/base' +require 'geocoder/results/ipdata_co' + +module Geocoder::Lookup + class IpdataCo < Base + + def name + "ipdata.co" + end + + def supported_protocols + [:https] + end + + def query_url(query) + "#{protocol}://#{host}/#{query.sanitized_text}" + end + + private # --------------------------------------------------------------- + + def results(query) + Geocoder.configure(:ipdata_co => {:http_headers => { "api-key" => configuration.api_key }}) if configuration.api_key + # don't look up a loopback address, just return the stored result + return [reserved_result(query.text)] if query.loopback_ip_address? + # note: Ipdata.co returns plain text on bad request + (doc = fetch_data(query)) ? [doc] : [] + end + + def reserved_result(ip) + { + "ip" => ip, + "city" => "", + "region_code" => "", + "region_name" => "", + "metrocode" => "", + "zipcode" => "", + "latitude" => "0", + "longitude" => "0", + "country_name" => "Reserved", + "country_code" => "RD" + } + end + + def host + "api.ipdata.co" + end + + def check_response_for_errors!(response) + if response.code.to_i == 403 + raise_error(Geocoder::RequestDenied) || + Geocoder.log(:warn, "Geocoding API error: 403 API key does not exist") + else + super(response) + end + end + end +end diff --git a/lib/geocoder/results/ipdata_co.rb b/lib/geocoder/results/ipdata_co.rb new file mode 100644 index 0000000000000000000000000000000000000000..497ac156d4c5441947b1e19870e60e41d85c77ff --- /dev/null +++ b/lib/geocoder/results/ipdata_co.rb @@ -0,0 +1,45 @@ +require 'geocoder/results/base' + +module Geocoder::Result + class IpdataCo < Base + + def address(format = :full) + s = state_code.to_s == "" ? "" : ", #{state_code}" + "#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, "") + end + + def city + @data['city'] + end + + def state + @data['region'] + end + + def state_code + @data['region_code'] + end + + def country + @data['country_name'] + end + + def country_code + @data['country_code'] + end + + def postal_code + @data['postal'] + end + + def self.response_attributes + %w[ip asn organisation currency currency_symbol calling_code flag time_zone is_eu] + end + + response_attributes.each do |a| + define_method a do + @data[a] + end + end + end +end diff --git a/test/fixtures/ipdata_co_74_200_247_59 b/test/fixtures/ipdata_co_74_200_247_59 new file mode 100644 index 0000000000000000000000000000000000000000..a2da12b53fbe78ef8d97a2bb219c80e7af35c706 --- /dev/null +++ b/test/fixtures/ipdata_co_74_200_247_59 @@ -0,0 +1,24 @@ +{ + "ip": "74.200.247.59", + "city": "Jersey City", + "region": "New Jersey", + "region_code": "NJ", + "country_name": "United States", + "country_code": "US", + "continent_name": "North America", + "continent_code": "NA", + "latitude": 40.7209, + "longitude": -74.0468, + "asn": "AS22576", + "organisation": "DataPipe, Inc.", + "postal": "07302", + "currency": "USD", + "currency_symbol": "$", + "calling_code": "1", + "flag": "https://ipdata.co/flags/us.png", + "time_zone": "America/New_York", + "is_eu": false, + "suspicious_factors": { + "is_tor": false + } +} \ No newline at end of file diff --git a/test/fixtures/ipdata_co_8_8_8 b/test/fixtures/ipdata_co_8_8_8 new file mode 100644 index 0000000000000000000000000000000000000000..2462e1270a88da040f6f434f9f52faf215522c5f --- /dev/null +++ b/test/fixtures/ipdata_co_8_8_8 @@ -0,0 +1 @@ +8.8.8 does not appear to be an IPv4 or IPv6 address \ No newline at end of file diff --git a/test/fixtures/ipdata_co_no_results b/test/fixtures/ipdata_co_no_results new file mode 100644 index 0000000000000000000000000000000000000000..896eaf56355459a4b59807ba85e079402455917f --- /dev/null +++ b/test/fixtures/ipdata_co_no_results @@ -0,0 +1 @@ +0.0.0 does not appear to be an IPv4 or IPv6 address \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 87e11da448827acbaeec7ebb5a45b9c9516fb826..4857dfed71f30051ee1e0abd24a840b8257576bb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -373,6 +373,14 @@ module Geocoder end end + require 'geocoder/lookups/ipdata_co' + class IpdataCo + private + def default_fixture_filename + "ipdata_co_74_200_247_59" + end + end + require 'geocoder/lookups/ban_data_gouv_fr' class BanDataGouvFr private diff --git a/test/unit/error_handling_test.rb b/test/unit/error_handling_test.rb index ca20f3e5e40caa3e0bd1118e3b490c86219d0070..689d1fe56c46d6ddfbb9f472d1ab4c863d9ea6e2 100644 --- a/test/unit/error_handling_test.rb +++ b/test/unit/error_handling_test.rb @@ -19,7 +19,7 @@ class ErrorHandlingTest < GeocoderTestCase def test_always_raise_response_parse_error Geocoder.configure(:always_raise => [Geocoder::ResponseParseError]) - [:freegeoip, :google, :okf].each do |l| + [:freegeoip, :google, :ipdata_co, :okf].each do |l| lookup = Geocoder::Lookup.get(l) set_api_key!(l) assert_raises Geocoder::ResponseParseError do @@ -29,7 +29,7 @@ class ErrorHandlingTest < GeocoderTestCase end def test_never_raise_response_parse_error - [:freegeoip, :google, :okf].each do |l| + [:freegeoip, :google, :ipdata_co, :okf].each do |l| lookup = Geocoder::Lookup.get(l) set_api_key!(l) silence_warnings do diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb index 8dcaf8047fa61f1d239a10d50eae2c63a5ba0571..7f21877f77b4359c3fe6b8c37a70ac5e123214c9 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, :ipapi_com].include? l # does not use query string + next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipdata_co, :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/ipdata_co_test.rb b/test/unit/lookups/ipdata_co_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..5acdcf72e7b5c19fdb5ac1e57e3402fc2a4fed21 --- /dev/null +++ b/test/unit/lookups/ipdata_co_test.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 +require 'test_helper' + +class IpdataCoTest < GeocoderTestCase + + def setup + Geocoder.configure(ip_lookup: :ipdata_co) + end + + def test_result_on_ip_address_search + result = Geocoder.search("74.200.247.59").first + assert result.is_a?(Geocoder::Result::IpdataCo) + end + + def test_invalid_json + Geocoder.configure(:always_raise => [Geocoder::ResponseParseError]) + assert_raise Geocoder::ResponseParseError do + Geocoder.search("8.8.8", ip_address: true) + end + 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_not_authorized + Geocoder.configure(always_raise: [Geocoder::RequestDenied]) + lookup = Geocoder::Lookup.get(:ipdata_co) + assert_raises Geocoder::RequestDenied do + response = MockHttpResponse.new(code: 403) + lookup.send(:check_response_for_errors!, response) + end + end +end