File Coverage

blib/lib/Geo/IPinfo.pm
Criterion Covered Total %
statement 26 117 22.2
branch 0 38 0.0
condition 0 3 0.0
subroutine 9 20 45.0
pod 5 5 100.0
total 40 183 21.8


line stmt bran cond sub pod time code
1             package Geo::IPinfo;
2              
3 1     1   64469 use 5.006;
  1         3  
4 1     1   6 use strict;
  1         1  
  1         59  
5 1     1   7 use warnings;
  1         2  
  1         35  
6 1     1   449 use Cache::LRU;
  1         615  
  1         29  
7 1     1   636 use LWP::UserAgent;
  1         47519  
  1         34  
8 1     1   8 use HTTP::Headers;
  1         2  
  1         21  
9 1     1   657 use JSON;
  1         9988  
  1         8  
10 1     1   530 use File::Share ':all';
  1         25577  
  1         148  
11 1     1   443 use Geo::Details;
  1         3  
  1         1214  
12              
13             our $VERSION = '1.2.0';
14             my $DEFAULT_CACHE_MAX_SIZE = 4096;
15             my $DEFAULT_CACHE_TTL = 86400;
16             my $DEFAULT_COUNTRY_FILE = 'countries.json';
17             my $DEFAULT_TIMEOUT = 2;
18              
19             my %valid_fields = (
20             ip => 1,
21             hostname => 1,
22             city => 1,
23             region => 1,
24             country => 1,
25             loc => 1,
26             org => 1,
27             postal => 1,
28             phone => 1,
29             geo => 1
30             );
31             my $base_url = 'https://ipinfo.io/';
32              
33             my $cache_ttl = 0;
34             my $custom_cache = 0;
35              
36             #-------------------------------------------------------------------------------
37              
38             sub new
39             {
40 0     0 1   my ($pkg, $token, %options) = @_;
41              
42 0           my $self = {};
43              
44 0           $self->{base_url} = $base_url;
45 0           $self->{ua} = LWP::UserAgent->new;
46 0           $self->{ua}->ssl_opts("verify_hostname" => 0);
47 0           $self->{ua}->default_headers(HTTP::Headers->new(
48             Accept => "application/json",
49             Authorization => "Bearer " . $token
50             ));
51 0           $self->{ua}->agent("IPinfoClient/Perl/$VERSION");
52              
53 0 0         my $timeout = defined $options{"timeout"} ? $options{"timeout"} : $DEFAULT_TIMEOUT;
54 0           $self->{ua}->timeout($timeout);
55              
56 0           $self->{message} = "";
57              
58 0           bless($self, $pkg);
59              
60 0           $self->{countries} = $self->_get_countries(%options);
61 0           $self->{cache} = $self->_build_cache(%options);
62              
63 0           return $self;
64             }
65              
66             #-------------------------------------------------------------------------------
67              
68             sub info
69             {
70 0     0 1   my ($self, $ip) = @_;
71              
72 0           return $self->_get_info($ip, "");
73             }
74              
75             #-------------------------------------------------------------------------------
76              
77             sub geo
78             {
79 0     0 1   my ($self, $ip) = @_;
80              
81 0           return $self->_get_info($ip, "geo");
82             }
83              
84             #-------------------------------------------------------------------------------
85              
86             sub field
87             {
88 0     0 1   my ($self, $ip, $field) = @_;
89              
90 0 0         if (not defined $field)
91             {
92 0           $self->{message} = "Field must be defined.";
93 0           return undef;
94             }
95              
96 0 0         if (not defined $valid_fields{$field})
97             {
98 0           $self->{message} = "Invalid field: $field";
99 0           return undef;
100             }
101              
102 0           return $self->_get_info($ip, $field);
103             }
104              
105             #-------------------------------------------------------------------------------
106              
107             sub error_msg
108             {
109 0     0 1   my $self = shift;
110              
111 0           return $self->{message};
112             }
113              
114             #-------------------------------------------------------------------------------
115             #-- private method(s) below , don't call them directly -------------------------
116              
117             sub _get_info
118             {
119 0     0     my ($self, $ip, $field) = @_;
120              
121 0 0         $ip = defined $ip ? $ip : "";
122 0 0         $field = defined $field ? $field : "";
123              
124 0           my ($info, $message) = $self->_lookup_info($ip, $field);
125 0           $self->{message} = $message;
126              
127 0 0         return defined $info ? Geo::Details->new($info) : undef;
128             }
129              
130             sub _lookup_info
131             {
132 0     0     my ($self, $ip, $field) = @_;
133              
134 0           my $key = $ip . "/" . $field;
135 0           my $cached_info = $self->_lookup_info_from_cache($key);
136              
137 0 0         if (defined $cached_info)
138             {
139 0           return ($cached_info, "");
140             }
141              
142 0           my ($source_info, $message) = $self->_lookup_info_from_source($key);
143 0 0         if (not defined $source_info)
144             {
145 0           return ($source_info, $message);
146             }
147              
148 0           my $country = $source_info->{"country"};
149 0 0         if (defined $country)
150             {
151 0           $source_info->{"country_name"} = $self->{countries}->{$country};
152             }
153              
154 0 0         if (defined $source_info->{"loc"})
155             {
156 0           my ($lat, $lon) = split(/,/, $source_info->{"loc"});
157 0           $source_info->{"latitude"} = $lat;
158 0           $source_info->{"longitude"} = $lon;
159             }
160              
161 0           $source_info->{"meta"} = {"time" => time(), "from_cache" => 0};
162 0           $self->{cache}->set($key, $source_info);
163              
164 0           return ($source_info, $message);
165             }
166              
167             sub _lookup_info_from_cache
168             {
169 0     0     my ($self, $cache_key) = @_;
170              
171 0           my $cached_info = $self->{cache}->get($cache_key);
172 0 0         if (defined $cached_info)
173             {
174 0           my $timedelta = time() - $cached_info->{"meta"}->{"time"};
175 0 0 0       if ($timedelta <= $cache_ttl || $custom_cache == 1)
176             {
177 0           $cached_info->{"meta"}->{"from_cache"} = 1;
178              
179 0           return $cached_info;
180             }
181             }
182              
183 0           return undef;
184             }
185              
186             sub _lookup_info_from_source
187             {
188 0     0     my ($self, $key) = @_;
189              
190 0           my $url = $self->{base_url} . $key;
191 0           my $response = $self->{ua}->get($url);
192              
193 0 0         if ($response->is_success)
194             {
195 0           print $response->decoded_content;
196 0           my $info = from_json($response->decoded_content);
197              
198 0           return ($info, "");
199             }
200 0 0         if ($response->code == 429)
201             {
202 0           return (undef, "Your monthly request quota has been exceeded.");
203             }
204              
205 0           return (undef, $response->status_line);
206             }
207              
208             sub _get_countries
209             {
210 0     0     my ($pkg, %options) = @_;
211 0           my $filename = undef;
212 0           my $data_location = undef;
213 0 0         if (defined $options{'countries'})
214             {
215 0           $filename = $options{'countries'};
216 0           $data_location = $filename;
217             }
218             else
219             {
220 0           $filename = $DEFAULT_COUNTRY_FILE;
221 0           $data_location = dist_file('Geo-IPinfo', $filename);
222             }
223              
224 0           my $json_text = do {
225 0 0         open(my $fh, '<', $data_location)
226             or die "Could not open file: $filename $!\n";
227 0           local $/;
228 0           <$fh>;
229             };
230              
231 0           return decode_json($json_text);
232             }
233              
234             sub _build_cache
235             {
236 0     0     my ($pkg, %options) = @_;
237              
238 0 0         if (defined $options{'cache'})
239             {
240 0           $custom_cache = 1;
241              
242 0           return $options{'cache'};
243             }
244              
245 0           $cache_ttl = $DEFAULT_CACHE_TTL;
246 0 0         if (defined $options{'cache_ttl'})
247             {
248 0           $cache_ttl = $options{'cache_ttl'};
249             }
250              
251             return Cache::LRU->new(
252             size => defined $options{'cache_max_size'} ?
253 0 0         $options{'cache_max_size'} : $DEFAULT_CACHE_MAX_SIZE
254             );
255             }
256             #-------------------------------------------------------------------------------
257              
258             1;
259             __END__