App-ipinfo
view release on metacpan or search on metacpan
lib/App/ipinfo.pm view on Meta::CPAN
=item * C<%n> - the country name of the organization
=item * C<%N> - newline
=item * C<%o> - the organization name
=item * C<%r> - the region of the organization (i.e. state or province)
=item * C<%t> - the timezone of the organization (e.g. C<America/New_York> )
=item * C<%T> - tab
=item * C<%%> - literal percent
=back
=head2 Class methods
=over 4
=item * new( HASH )
Allowed keys:
=over 4
=item * error_fh
The filehandle to send error output to. The default is standard error.
=item * template
The template.
=item * output_fh
The filehandle to send error output to. The default is standard output.
=item * token
The API token from IPinfo.io.
=back
=cut
sub new ($class, %hash) {
state $defaults = {
output_fh => $class->default_output_fh,
error_fh => $class->default_error_fh,
template => $class->default_template,
token => $class->get_token,
};
my %args = ( $defaults->%*, %hash );
bless \%args, $class;
}
=item * looks_like_template(STRING)
Returns true if STRING looks like a template. That is, it has a C<%>
followed by a non-whitespace character. This will get more sophisticated
later.
=cut
sub looks_like_template ($either, $string) {
$string =~ m/%\S/;
}
=item * CLASS->run( [TEMPLATE,] IP_ADDRESS [, IP_ADDRESS ... ] )
=item * OBJ->run( [TEMPLATE,] IP_ADDRESS [, IP_ADDRESS ... ] )
Format every IP address according to TEMPLATE and send the result to
the output filehandle.
If the first argument looks like a template (has a C<%>), it is used
to format the output. Otherwise, the first argument is taken as the start
of the list of IP addresses and the default format is used.
If the invocant is not a reference, it's used as the class name to
build the object. If the invocant is a reference, it's used as the
object. These are the same and use all the default settings:
my $obj = App::ipinfo->new;
$obj->run( @ip_addresses );
App::ipfinfo->run( @ip_addresses );
=cut
sub run ($either, @args) {
my $opts = ref $args[0] eq ref {} ? shift @args : {};
my $app = ref $either ? $either : $either->new($opts->%*);
ARG: foreach my $ip (@args) {
my $info = $app->get_info($ip);
next ARG unless eval { $info->isa('Geo::Details') };
$app->output( $app->format( $info ) );
}
}
=back
=head2 Instance methods
=over 4
=cut
# https://stackoverflow.com/a/45943193/2766176
sub _compact_ipv6 {
# taken from IPv6::Address on CPAN
my $str = shift;
return '::' if($str eq '0:0:0:0:0:0:0:0');
for(my $i=7;$i>1;$i--) {
my $zerostr = join(':',split('','0'x$i));
###print "DEBUG: $str $zerostr \n";
if($str =~ /:$zerostr$/) {
$str =~ s/:$zerostr$/::/;
return $str;
}
elsif ($str =~ /:$zerostr:/) {
$str =~ s/:$zerostr:/::/;
return $str;
}
elsif ($str =~ /^$zerostr:/) {
$str =~ s/^$zerostr:/::/;
return $str;
}
}
return $str;
}
=item * decode_info
Fixup some issues in the API response.
lib/App/ipinfo.pm view on Meta::CPAN
sprintf "%${w}s", $V->[0]->continent->{name} // '';
},
L => sub ( $w, $v, $V, $l ) {
defined $V->[0]->latitude ?
sprintf "%${w}f", $V->[0]->latitude
:
'';
},
l => sub ( $w, $v, $V, $l ) {
defined $V->[0]->longitude ?
sprintf "%${w}f", $V->[0]->longitude
:
'';
},
n => sub ( $w, $v, $V, $l ) {
sprintf "%${w}s", $V->[0]->country_name // '';
},
o => sub ( $w, $v, $V, $l ) {
sprintf "%${w}s", $V->[0]->org // '';
},
r => sub ( $w, $v, $V, $l ) {
sprintf "%${w}s", $V->[0]->region // '';
},
t => sub ( $w, $v, $V, $l ) {
sprintf "%${w}s", $V->[0]->timezone // '';
},
N => sub { "\n" },
T => sub { "\t" },
);
}
=item * format( TEMPLATE, IP_INFO )
Formats a L<Geo::Details> object according to template.
=cut
sub format ($app, $info) {
state $formatter = $app->formatter;
$formatter->sprintf( $app->template, $info );
}
=item * get_info(IP_ADDRESS)
=cut
sub get_info ($app, $ip ) {
state $ipinfo = do {
my $g = Geo::IPinfo->new( $app->token );
$g->{base_url_ipv6} = $g->{base_url};
$g;
};
my $method = do {
if( $app->looks_like_ipv4($ip) ) {
'info';
}
elsif( $app->looks_like_ipv6($ip) ) {
$ip = _compact_ipv6($ip);
'info_v6'
}
else {
$app->error( "<$ip> does not look like an IP address. Skipping." );
return;
}
};
my $info = $ipinfo->$method($ip);
# https://github.com/ipinfo/perl/pull/32
# cache hit is doubly wrapped in object
my @values = grep { eval { $_->isa('Geo::Details') } } values %$info;
$info = shift @values if @values;
unless( defined $info and eval { $info->isa('Geo::Details') } ) {
$app->error( "Could not get info for <$ip>." );
return;
}
if( exists $info->{bogon} and $info->{bogon} eq 'True' ) {
$app->error( "<$ip> is a bogon." );
return;
}
$app->decode_info($info);
return $info;
}
=item * looks_like_ipv4(IP)
Returns true if IP looks like an IPv4 address.
=cut
sub looks_like_ipv4 ($app, $ip) {
Net::CIDR::cidrvalidate($ip);
}
=item * looks_like_ipv6(IP)
Returns true if IP looks like an IPv6 address.
=cut
sub looks_like_ipv6 ($app, $ip) {
my $compact = _compact_ipv6($ip);
Net::CIDR::cidrvalidate($compact);
}
=item * get_token
Return the API token. So far, this is just the value in the C<APP_IPINFO_TOKEN>
environment variable.
=cut
sub get_token ($class) {
$ENV{APP_IPINFO_TOKEN}
}
=item * output(MESSAGE)
Send the MESSAGE string to the output filehandle.
=cut
sub output ($app, $message) {
print { $app->output_fh } $message
}
=item * output_fh
Return the filehandle for output.
=cut
sub output_fh ($app) { $app->{output_fh} }
=item * template
=cut
sub template ($app) { $app->{template} }
=item * token
Return the IPinfo.io token
=cut
sub token ($app) { $app->{token} }
=back
=head1 SEE ALSO
=over 4
=item * L<Geo::IPinfo>
=item * IPinfo.io, L<https://ipinfo.io>
=back
=head1 SOURCE AVAILABILITY
( run in 0.967 second using v1.01-cache-2.11-cpan-98e64b0badf )