diff --git a/CHANGELOG.md b/CHANGELOG.md index 93aa2c3..52820be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.6.0 + +* A new `residential` object has been added to the `anonymizer` object on + `MaxMind::GeoIP2::Model::Insights`. This object contains data from the + GeoIP Residential Proxy database, including confidence scoring, provider + name detection, and network last seen date for residential proxies. This + is only available from the GeoIP2 Insights web service. + ## 1.5.1 (2026-01-19) * Re-release with a fix to the release process. This includes a bump of the diff --git a/lib/maxmind/geoip2/record/anonymizer.rb b/lib/maxmind/geoip2/record/anonymizer.rb index 073b992..06ac555 100644 --- a/lib/maxmind/geoip2/record/anonymizer.rb +++ b/lib/maxmind/geoip2/record/anonymizer.rb @@ -2,6 +2,7 @@ require 'date' require 'maxmind/geoip2/record/abstract' +require 'maxmind/geoip2/record/anonymizer_feed' module MaxMind module GeoIP2 @@ -11,6 +12,20 @@ module Record # # This record is returned by the Insights web service. class Anonymizer < Abstract + # Data about the residential proxy status of the IP address, as + # sourced from the GeoIP Residential Proxy database. This property is + # only available from Insights. + # + # @return [MaxMind::GeoIP2::Record::AnonymizerFeed] + attr_reader :residential + + # @!visibility private + def initialize(record) + super + + @residential = AnonymizerFeed.new(record.nil? ? nil : record['residential']) + end + # A score ranging from 1 to 99 that represents our percent confidence # that the network is currently part of an actively used VPN service. # This property is only available from Insights. diff --git a/lib/maxmind/geoip2/record/anonymizer_feed.rb b/lib/maxmind/geoip2/record/anonymizer_feed.rb new file mode 100644 index 0000000..bdc0464 --- /dev/null +++ b/lib/maxmind/geoip2/record/anonymizer_feed.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'date' +require 'maxmind/geoip2/record/abstract' + +module MaxMind + module GeoIP2 + module Record + # Contains data from an anonymizer data feed, such as the residential + # proxy feed. + # + # This record is returned by the anonymizer object of the Insights web + # service. + class AnonymizerFeed < Abstract + # A score ranging from 1 to 99 that represents our percent confidence + # that the network is currently part of this anonymizer feed. This + # property is only available from Insights. + # + # @return [Integer, nil] + def confidence + get('confidence') + end + + # The last day that the network was sighted in this anonymizer feed. + # This value is parsed lazily. This property is only available from + # Insights. + # + # @return [Date, nil] A Date object representing the last seen date, + # or nil if the date is not available. + def network_last_seen + return @network_last_seen if defined?(@network_last_seen) + + date_string = get('network_last_seen') + + if !date_string + return nil + end + + @network_last_seen = Date.parse(date_string) + end + + # The name of the provider associated with the network in this + # anonymizer feed. This property is only available from Insights. + # + # @return [String, nil] + def provider_name + get('provider_name') + end + end + end + end +end diff --git a/test/test_client.rb b/test/test_client.rb index c372011..1505e30 100644 --- a/test/test_client.rb +++ b/test/test_client.rb @@ -38,6 +38,11 @@ class ClientTest < Minitest::Test 'is_tor_exit_node' => false, 'network_last_seen' => '2025-10-15', 'provider_name' => 'nordvpn', + 'residential' => { + 'confidence' => 82, + 'network_last_seen' => '2026-05-11', + 'provider_name' => 'quickshift', + }, }, 'continent' => { 'code' => 'NA', @@ -109,6 +114,11 @@ def test_insights assert_equal(Date.parse('2025-10-15'), record.anonymizer.network_last_seen) assert_equal('nordvpn', record.anonymizer.provider_name) + # Test anonymizer.residential object + assert_equal(82, record.anonymizer.residential.confidence) + assert_equal(Date.parse('2026-05-11'), record.anonymizer.residential.network_last_seen) + assert_equal('quickshift', record.anonymizer.residential.provider_name) + # Test traits assert(record.traits.anycast?) assert(record.traits.residential_proxy?)