File Coverage

blib/lib/DNS/Robot.pm
Criterion Covered Total %
statement 42 74 56.7
branch 13 32 40.6
condition 6 11 54.5
subroutine 17 20 85.0
pod 12 12 100.0
total 90 149 60.4


line stmt bran cond sub pod time code
1             package DNS::Robot;
2              
3 2     2   238476 use strict;
  2         4  
  2         79  
4 2     2   15 use warnings;
  2         4  
  2         108  
5              
6 2     2   1695 use HTTP::Tiny;
  2         149204  
  2         162  
7 2     2   1568 use JSON::PP qw(encode_json decode_json);
  2         42107  
  2         216  
8 2     2   20 use Carp qw(croak);
  2         3  
  2         3027  
9              
10             our $VERSION = '0.01';
11              
12             sub new {
13 4     4 1 356994 my ($class, %args) = @_;
14             my $self = bless {
15             base_url => $args{base_url} || 'https://dnsrobot.net/api',
16             user_agent => $args{user_agent} || "DNS-Robot-Perl/$VERSION",
17 4   100     80 timeout => $args{timeout} || 30,
      66        
      100        
18             }, $class;
19             $self->{http} = HTTP::Tiny->new(
20             agent => $self->{user_agent},
21             timeout => $self->{timeout},
22 4         46 );
23 4         415 return $self;
24             }
25              
26             # ── Internal helpers ──
27              
28             sub _post {
29 0     0   0 my ($self, $endpoint, $payload) = @_;
30 0         0 my $url = "$self->{base_url}/$endpoint";
31 0         0 my $body = encode_json($payload);
32 0         0 my $res = $self->{http}->request('POST', $url, {
33             headers => { 'Content-Type' => 'application/json' },
34             content => $body,
35             });
36             croak "DNS::Robot: HTTP $res->{status} from $endpoint"
37 0 0       0 unless $res->{success};
38 0         0 return decode_json($res->{content});
39             }
40              
41             sub _get {
42 0     0   0 my ($self, $endpoint, $params) = @_;
43 0         0 my $query = join '&', map { "$_=" . _uri_encode($params->{$_}) } sort keys %$params;
  0         0  
44 0         0 my $url = "$self->{base_url}/$endpoint?$query";
45 0         0 my $res = $self->{http}->request('GET', $url);
46             croak "DNS::Robot: HTTP $res->{status} from $endpoint"
47 0 0       0 unless $res->{success};
48 0         0 return decode_json($res->{content});
49             }
50              
51             sub _uri_encode {
52 0     0   0 my ($str) = @_;
53 0         0 $str =~ s/([^A-Za-z0-9\-._~])/sprintf("%%%02X", ord($1))/ge;
  0         0  
54 0         0 return $str;
55             }
56              
57             # ── Public methods ──
58              
59             sub dns_lookup {
60 1     1 1 8 my ($self, %args) = @_;
61 1 50       177 croak "dns_lookup: 'domain' is required" unless $args{domain};
62             return $self->_post('dns-query', {
63             domain => $args{domain},
64             recordType => $args{record_type} || 'A',
65 0   0     0 dnsServer => $args{dns_server} || '8.8.8.8',
      0        
66             });
67             }
68              
69             sub whois_lookup {
70 1     1 1 1327 my ($self, %args) = @_;
71 1 50       107 croak "whois_lookup: 'domain' is required" unless $args{domain};
72 0         0 return $self->_post('whois', { domain => $args{domain} });
73             }
74              
75             sub ssl_check {
76 1     1 1 803 my ($self, %args) = @_;
77 1 50       128 croak "ssl_check: 'domain' is required" unless $args{domain};
78 0         0 return $self->_post('ssl-certificate', { domain => $args{domain} });
79             }
80              
81             sub spf_check {
82 1     1 1 792 my ($self, %args) = @_;
83 1 50       88 croak "spf_check: 'domain' is required" unless $args{domain};
84 0         0 return $self->_post('spf-checker', { domain => $args{domain} });
85             }
86              
87             sub dkim_check {
88 1     1 1 873 my ($self, %args) = @_;
89 1 50       88 croak "dkim_check: 'domain' is required" unless $args{domain};
90 0         0 my $payload = { domain => $args{domain} };
91 0 0       0 $payload->{selector} = $args{selector} if $args{selector};
92 0         0 return $self->_post('dkim-checker', $payload);
93             }
94              
95             sub dmarc_check {
96 1     1 1 834 my ($self, %args) = @_;
97 1 50       87 croak "dmarc_check: 'domain' is required" unless $args{domain};
98 0         0 return $self->_post('dmarc-checker', { domain => $args{domain} });
99             }
100              
101             sub mx_lookup {
102 1     1 1 829 my ($self, %args) = @_;
103 1 50       89 croak "mx_lookup: 'domain' is required" unless $args{domain};
104 0         0 return $self->_post('mx-lookup', { domain => $args{domain} });
105             }
106              
107             sub ns_lookup {
108 1     1 1 759 my ($self, %args) = @_;
109 1 50       132 croak "ns_lookup: 'domain' is required" unless $args{domain};
110 0         0 return $self->_post('ns-lookup', { domain => $args{domain} });
111             }
112              
113             sub ip_lookup {
114 1     1 1 784 my ($self, %args) = @_;
115 1 50       88 croak "ip_lookup: 'ip' is required" unless $args{ip};
116 0         0 return $self->_post('ip-info', { ip => $args{ip} });
117             }
118              
119             sub http_headers {
120 1     1 1 808 my ($self, %args) = @_;
121 1 50       112 croak "http_headers: 'url' is required" unless $args{url};
122 0         0 my $url = $args{url};
123 0 0       0 $url = "https://$url" unless $url =~ m{^https?://};
124 0         0 return $self->_post('http-headers', { url => $url });
125             }
126              
127             sub port_check {
128 2     2 1 1834 my ($self, %args) = @_;
129 2 100       122 croak "port_check: 'host' is required" unless $args{host};
130 1 50       139 croak "port_check: 'port' is required" unless $args{port};
131             return $self->_get('port-check', {
132             host => $args{host},
133             port => $args{port},
134 0           });
135             }
136              
137             1;
138              
139             __END__