|
| 1 | +package Geo::DetailsPlus; |
| 2 | + |
| 3 | +use 5.006; |
| 4 | +use strict; |
| 5 | +use warnings; |
| 6 | + |
| 7 | +# Helper package for GeoPlus data |
| 8 | +package Geo::DetailsPlus::Geo { |
| 9 | + use strict; |
| 10 | + use warnings; |
| 11 | + |
| 12 | + sub new { |
| 13 | + my $class = shift; |
| 14 | + my $data = shift || {}; |
| 15 | + bless $data, $class; |
| 16 | + return $data; |
| 17 | + } |
| 18 | + |
| 19 | + sub city { return $_[0]->{city}; } |
| 20 | + sub region { return $_[0]->{region}; } |
| 21 | + sub region_code { return $_[0]->{region_code}; } |
| 22 | + sub country { return $_[0]->{country}; } |
| 23 | + sub country_code { return $_[0]->{country_code}; } |
| 24 | + sub continent { return $_[0]->{continent}; } |
| 25 | + sub continent_code { return $_[0]->{continent_code}; } |
| 26 | + sub latitude { return $_[0]->{latitude}; } |
| 27 | + sub longitude { return $_[0]->{longitude}; } |
| 28 | + sub timezone { return $_[0]->{timezone}; } |
| 29 | + sub postal_code { return $_[0]->{postal_code}; } |
| 30 | + sub dma_code { return $_[0]->{dma_code}; } |
| 31 | + sub geoname_id { return $_[0]->{geoname_id}; } |
| 32 | + sub radius { return $_[0]->{radius}; } |
| 33 | + sub last_changed { return $_[0]->{last_changed}; } |
| 34 | + |
| 35 | + # Enriched fields |
| 36 | + sub country_name { return $_[0]->{country_name}; } |
| 37 | + sub is_eu { return $_[0]->{is_eu}; } |
| 38 | + sub country_flag { return $_[0]->{country_flag}; } |
| 39 | + sub country_flag_url { return $_[0]->{country_flag_url}; } |
| 40 | + sub country_currency { return $_[0]->{country_currency}; } |
| 41 | + sub continent_info { return $_[0]->{continent_info}; } |
| 42 | +} |
| 43 | + |
| 44 | +# Helper package for ASPlus data |
| 45 | +package Geo::DetailsPlus::AS { |
| 46 | + use strict; |
| 47 | + use warnings; |
| 48 | + |
| 49 | + sub new { |
| 50 | + my $class = shift; |
| 51 | + my $data = shift || {}; |
| 52 | + bless $data, $class; |
| 53 | + return $data; |
| 54 | + } |
| 55 | + |
| 56 | + sub asn { return $_[0]->{asn}; } |
| 57 | + sub name { return $_[0]->{name}; } |
| 58 | + sub domain { return $_[0]->{domain}; } |
| 59 | + sub type { return $_[0]->{type}; } |
| 60 | + sub last_changed { return $_[0]->{last_changed}; } |
| 61 | +} |
| 62 | + |
| 63 | +# Helper package for Mobile data |
| 64 | +package Geo::DetailsPlus::Mobile { |
| 65 | + use strict; |
| 66 | + use warnings; |
| 67 | + |
| 68 | + sub new { |
| 69 | + my $class = shift; |
| 70 | + my $data = shift || {}; |
| 71 | + bless $data, $class; |
| 72 | + return $data; |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +# Helper package for Anonymous data |
| 77 | +package Geo::DetailsPlus::Anonymous { |
| 78 | + use strict; |
| 79 | + use warnings; |
| 80 | + |
| 81 | + sub new { |
| 82 | + my $class = shift; |
| 83 | + my $data = shift || {}; |
| 84 | + bless $data, $class; |
| 85 | + return $data; |
| 86 | + } |
| 87 | + |
| 88 | + sub is_proxy { return $_[0]->{is_proxy}; } |
| 89 | + sub is_relay { return $_[0]->{is_relay}; } |
| 90 | + sub is_tor { return $_[0]->{is_tor}; } |
| 91 | + sub is_vpn { return $_[0]->{is_vpn}; } |
| 92 | +} |
| 93 | + |
| 94 | +# Helper package for Abuse data |
| 95 | +package Geo::DetailsPlus::Abuse { |
| 96 | + use strict; |
| 97 | + use warnings; |
| 98 | + |
| 99 | + sub new { |
| 100 | + my $class = shift; |
| 101 | + my $data = shift || {}; |
| 102 | + bless $data, $class; |
| 103 | + return $data; |
| 104 | + } |
| 105 | + |
| 106 | + sub address { return $_[0]->{address}; } |
| 107 | + sub country { return $_[0]->{country}; } |
| 108 | + sub email { return $_[0]->{email}; } |
| 109 | + sub name { return $_[0]->{name}; } |
| 110 | + sub network { return $_[0]->{network}; } |
| 111 | + sub phone { return $_[0]->{phone}; } |
| 112 | + |
| 113 | + # Enriched |
| 114 | + sub country_name { return $_[0]->{country_name}; } |
| 115 | +} |
| 116 | + |
| 117 | +# Helper package for Company data |
| 118 | +package Geo::DetailsPlus::Company { |
| 119 | + use strict; |
| 120 | + use warnings; |
| 121 | + |
| 122 | + sub new { |
| 123 | + my $class = shift; |
| 124 | + my $data = shift || {}; |
| 125 | + bless $data, $class; |
| 126 | + return $data; |
| 127 | + } |
| 128 | + |
| 129 | + sub name { return $_[0]->{name}; } |
| 130 | + sub domain { return $_[0]->{domain}; } |
| 131 | + sub type { return $_[0]->{type}; } |
| 132 | +} |
| 133 | + |
| 134 | +# Helper package for Privacy data |
| 135 | +package Geo::DetailsPlus::Privacy { |
| 136 | + use strict; |
| 137 | + use warnings; |
| 138 | + |
| 139 | + sub new { |
| 140 | + my $class = shift; |
| 141 | + my $data = shift || {}; |
| 142 | + bless $data, $class; |
| 143 | + return $data; |
| 144 | + } |
| 145 | + |
| 146 | + sub vpn { return $_[0]->{vpn}; } |
| 147 | + sub proxy { return $_[0]->{proxy}; } |
| 148 | + sub tor { return $_[0]->{tor}; } |
| 149 | + sub relay { return $_[0]->{relay}; } |
| 150 | + sub hosting { return $_[0]->{hosting}; } |
| 151 | + sub service { return $_[0]->{service}; } |
| 152 | +} |
| 153 | + |
| 154 | +# Helper package for Domains data |
| 155 | +package Geo::DetailsPlus::Domains { |
| 156 | + use strict; |
| 157 | + use warnings; |
| 158 | + |
| 159 | + sub new { |
| 160 | + my $class = shift; |
| 161 | + my $data = shift || {}; |
| 162 | + bless $data, $class; |
| 163 | + return $data; |
| 164 | + } |
| 165 | + |
| 166 | + sub domains { return $_[0]->{domains}; } |
| 167 | + sub total { return $_[0]->{total}; } |
| 168 | +} |
| 169 | + |
| 170 | +# Main package |
| 171 | +package Geo::DetailsPlus; |
| 172 | + |
| 173 | +sub new { |
| 174 | + my $class = shift; |
| 175 | + my $data = shift; |
| 176 | + my $key = shift // ''; |
| 177 | + |
| 178 | + # If $data is a hash reference, process and bless it |
| 179 | + if ( ref($data) eq 'HASH' ) { |
| 180 | + # Convert nested objects to blessed objects |
| 181 | + if ( exists $data->{geo} && ref($data->{geo}) eq 'HASH' ) { |
| 182 | + $data->{geo} = Geo::DetailsPlus::Geo->new($data->{geo}); |
| 183 | + } |
| 184 | + if ( exists $data->{as} && ref($data->{as}) eq 'HASH' ) { |
| 185 | + $data->{as} = Geo::DetailsPlus::AS->new($data->{as}); |
| 186 | + } |
| 187 | + if ( exists $data->{mobile} && ref($data->{mobile}) eq 'HASH' ) { |
| 188 | + $data->{mobile} = Geo::DetailsPlus::Mobile->new($data->{mobile}); |
| 189 | + } |
| 190 | + if ( exists $data->{anonymous} && ref($data->{anonymous}) eq 'HASH' ) { |
| 191 | + $data->{anonymous} = Geo::DetailsPlus::Anonymous->new($data->{anonymous}); |
| 192 | + } |
| 193 | + if ( exists $data->{abuse} && ref($data->{abuse}) eq 'HASH' ) { |
| 194 | + $data->{abuse} = Geo::DetailsPlus::Abuse->new($data->{abuse}); |
| 195 | + } |
| 196 | + if ( exists $data->{company} && ref($data->{company}) eq 'HASH' ) { |
| 197 | + $data->{company} = Geo::DetailsPlus::Company->new($data->{company}); |
| 198 | + } |
| 199 | + if ( exists $data->{privacy} && ref($data->{privacy}) eq 'HASH' ) { |
| 200 | + $data->{privacy} = Geo::DetailsPlus::Privacy->new($data->{privacy}); |
| 201 | + } |
| 202 | + if ( exists $data->{domains} && ref($data->{domains}) eq 'HASH' ) { |
| 203 | + $data->{domains} = Geo::DetailsPlus::Domains->new($data->{domains}); |
| 204 | + } |
| 205 | + |
| 206 | + bless $data, $class; |
| 207 | + return $data; |
| 208 | + } |
| 209 | + |
| 210 | + # If $data is a plain string, create a new hash reference |
| 211 | + my $self = { $key => $data }; |
| 212 | + bless $self, $class; |
| 213 | + return $self; |
| 214 | +} |
| 215 | + |
| 216 | +sub TO_JSON { |
| 217 | + my ($self) = @_; |
| 218 | + return {%$self}; |
| 219 | +} |
| 220 | + |
| 221 | +sub ip { return $_[0]->{ip}; } |
| 222 | +sub hostname { return $_[0]->{hostname}; } |
| 223 | +sub geo { return $_[0]->{geo}; } |
| 224 | +sub as { return $_[0]->{as}; } |
| 225 | +sub mobile { return $_[0]->{mobile}; } |
| 226 | +sub anonymous { return $_[0]->{anonymous}; } |
| 227 | +sub abuse { return $_[0]->{abuse}; } |
| 228 | +sub company { return $_[0]->{company}; } |
| 229 | +sub privacy { return $_[0]->{privacy}; } |
| 230 | +sub domains { return $_[0]->{domains}; } |
| 231 | +sub is_anonymous { return $_[0]->{is_anonymous}; } |
| 232 | +sub is_anycast { return $_[0]->{is_anycast}; } |
| 233 | +sub is_hosting { return $_[0]->{is_hosting}; } |
| 234 | +sub is_mobile { return $_[0]->{is_mobile}; } |
| 235 | +sub is_satellite { return $_[0]->{is_satellite}; } |
| 236 | +sub bogon { return $_[0]->{bogon}; } |
| 237 | + |
| 238 | +sub all { |
| 239 | + return $_[0]; |
| 240 | +} |
| 241 | + |
| 242 | +1; |
| 243 | +__END__ |
| 244 | +
|
| 245 | +=head1 NAME |
| 246 | +
|
| 247 | +Geo::DetailsPlus - Module to represent details of an IP returned by the Plus API |
| 248 | +
|
| 249 | +=head1 SYNOPSIS |
| 250 | +
|
| 251 | + use Geo::DetailsPlus; |
| 252 | +
|
| 253 | + my $data = { |
| 254 | + ip => '8.8.8.8', |
| 255 | + hostname => 'dns.google', |
| 256 | + geo => { |
| 257 | + city => 'Mountain View', |
| 258 | + country => 'United States', |
| 259 | + country_code => 'US', |
| 260 | + }, |
| 261 | + as => { |
| 262 | + asn => 'AS15169', |
| 263 | + name => 'Google LLC', |
| 264 | + }, |
| 265 | + privacy => { |
| 266 | + vpn => 0, |
| 267 | + proxy => 0, |
| 268 | + }, |
| 269 | + }; |
| 270 | +
|
| 271 | + my $details = Geo::DetailsPlus->new($data); |
| 272 | + print $details->ip; # Output: 8.8.8.8 |
| 273 | + print $details->hostname; # Output: dns.google |
| 274 | + print $details->geo->city; # Output: Mountain View |
| 275 | + print $details->privacy->vpn; # Output: 0 |
| 276 | +
|
| 277 | +=head1 DESCRIPTION |
| 278 | +
|
| 279 | +Geo::DetailsPlus represents details of an IP returned by the IPinfo Plus API. |
| 280 | +
|
| 281 | +=head1 AUTHOR |
| 282 | +
|
| 283 | +IPinfo <support@ipinfo.io> |
| 284 | +
|
| 285 | +=head1 COPYRIGHT AND LICENSE |
| 286 | +
|
| 287 | +Copyright (c) 2025 IPinfo |
| 288 | +
|
| 289 | +Licensed under the Apache License, Version 2.0. |
| 290 | +
|
| 291 | +=cut |
0 commit comments